Merge pull request #7825 from dotCooCoo/fix/nested-symbol-box-is-not-a-hit

Selection: a nested symbol instance's box past the outer box is not a hit
This commit is contained in:
BFlat
2026-11-15 20:45:25 +07:00
committed by GitHub
2 changed files with 324 additions and 11 deletions
+145 -11
View File
@@ -266,6 +266,7 @@ pub fn hit_test_visible_skipping(doc: &Document, p: Point, opt: HitOptions, skip
/// (`opt.scope`) or on a locked artboard. On a layer whose Preview is off it counts by its
/// outline, as a click there hits it.
pub fn selected_hidden_at(doc: &Document, p: Point, opt: HitOptions, selected: &[NodeId]) -> Option<NodeId> {
let _walk = ArtWalk::open();
let symbols = doc.symbol_boxes();
let locks = doc.locked_artboards();
selected.iter().rev().copied().find(|&id| {
@@ -322,6 +323,7 @@ fn path_options(nodes: &[&Node], chain: &[NodeId], opt: HitOptions) -> Option<Hi
/// The objects under `p` that [`Hit::top_object`] picks in `scope` (isolation mode), topmost
/// first: what clicks there select, one below the other (Cmd/Ctrl-click selects behind).
pub fn objects_at(doc: &Document, p: Point, opt: HitOptions, scope: Option<NodeId>) -> Vec<NodeId> {
let walk = ArtWalk::open();
let opt = HitOptions { scope: scope.or(opt.scope), ..opt };
// Normally one hit test per *object* suffices: layers and sublayers aren't selectable,
// while a group (including a clipping group) is selected as one object. Repeatedly
@@ -337,6 +339,7 @@ pub fn objects_at(doc: &Document, p: Point, opt: HitOptions, scope: Option<NodeI
for layer in doc.layers.iter().rev() {
// Malformed files may contain non-layer roots; let the general traversal handle them.
if !layer.is_layer() {
walk.restart();
return objects_at_repeated(doc, p, opt, scope);
}
if !layer.visible || layer.locked || layer.is_template() || doc.pattern_edit.as_ref().is_some_and(|e| e.layer != layer.id) {
@@ -344,7 +347,9 @@ pub fn objects_at(doc: &Document, p: Point, opt: HitOptions, scope: Option<NodeI
}
if !objects_at_layer(layer, p, opt, &symbols, &mut tops, &mut seen) {
// An envelope in Edit Contents may expose objects *within* a top-level group.
// The repeated traversal handles that mixed hierarchy precisely.
// The repeated traversal handles that mixed hierarchy precisely, with the whole
// limit of symbol art to walk.
walk.restart();
return objects_at_repeated(doc, p, opt, scope);
}
}
@@ -429,6 +434,7 @@ pub const MAX_LOCKED_AT: usize = 20;
/// out. While a pattern is edited only its tile layer is searched, and in isolation mode
/// (`opt.scope`) only what is inside the isolated group or object.
pub fn locked_objects_at(doc: &Document, p: Point, opt: HitOptions) -> Vec<NodeId> {
let _walk = ArtWalk::open();
let symbols = doc.symbol_boxes();
let mut out = Vec::new();
if let Some(scope) = opt.scope
@@ -506,6 +512,7 @@ fn object_hit(n: &Node, p: Point, opt: HitOptions, symbols: &SymbolBoxes, editab
/// an isolated group on one ([`crate::LockedArtboards::holds`]). Symbol instances are measured by their art
/// ([`Node::geometric_bounds_with`]), and are also hit outside that box where their art paints.
fn hit_test_skipping(doc: &Document, p: Point, opt: HitOptions, skip: &dyn Fn(NodeId) -> bool, editable_only: bool) -> Option<Hit> {
let _walk = ArtWalk::open();
let finish = |mut h: Hit| {
h.contents_of = h.ancestry.iter().rev().copied().find(|a| doc.node(*a).is_some_and(edits_contents));
h.layers = h.ancestry.iter().take_while(|a| doc.node(**a).is_some_and(Node::is_layer)).count();
@@ -1059,9 +1066,10 @@ fn hit_leaf(n: &Node, p: Point, opt: HitOptions, symbols: &SymbolBoxes) -> Optio
n.geometric_bounds_with(symbols).filter(|b| b.inflate(opt.tol, opt.tol).contains(p)).map(|_| HitKind::Bounds)
}
// A symbol instance is hit within its bounds, and outside them where its art paints
// ([`instance_art_hit`]).
// ([`instance_art_hit`]). In the art of another instance walked outside that instance's
// bounds, it is hit only where its own art paints ([`ArtOnly`]).
NodeKind::SymbolInstance { .. } => {
if n.geometric_bounds_with(symbols).is_some_and(|b| b.inflate(opt.tol, opt.tol).contains(p)) {
if !ArtOnly::on() && n.geometric_bounds_with(symbols).is_some_and(|b| b.inflate(opt.tol, opt.tol).contains(p)) {
return Some(HitKind::Bounds);
}
instance_art_hit(n, p, opt, symbols)
@@ -1172,6 +1180,15 @@ impl<'a> InstanceArt<'a> {
whole.or_else(|| self.map.as_ref()?.one_cell_with(n, &self.boxes))
}
/// How many objects placing the whole art makes: the objects of the art with their opacity
/// masks' art ([`Node::count_with_masks`]), or, through the guides, the most that
/// [`crate::NineSliceMap::most_placed_with`] counts.
fn size(&self) -> u64 {
self.map
.as_ref()
.map_or_else(|| u64::try_from(self.art.count_with_masks()).unwrap_or(u64::MAX), |m| m.most_placed_with(&self.art, &self.boxes))
}
/// Object `n` of the art placed on the canvas as it is drawn, clip groups included: moved by
/// `whole` when an object around it is moved as one piece ([`Self::whole`]). `None` when
/// placing it through the guides could make more than [`crate::MAX_SYMBOL_NODES`] objects, as
@@ -1219,6 +1236,7 @@ fn moved(n: &Node, xf: vectorcraft_geom::Affine) -> Node {
/// layer, is tested in outline ([`HitOptions::outline`]), as the canvas draws it. `None` when `inst` is not a symbol instance or
/// no editable object of its art is under `p`.
pub fn symbol_part_at(doc: &Document, inst: &Node, p: Point, opt: HitOptions) -> Option<Vec<u32>> {
let _walk = ArtWalk::open();
let art = InstanceArt::of(doc, inst)?;
let symbols = doc.symbol_boxes();
let mut path = vec![];
@@ -1250,7 +1268,7 @@ impl FindPart<'_> {
/// What a click at `p` hits on object `n` of the art, with `path` the child indices that lead
/// to what it hits. `outline`: the view is in Outline, or a layer around `n` has Preview off.
fn find(&self, n: &Node, path: &mut Vec<u32>, whole: Option<vectorcraft_geom::Affine>, outline: bool) -> Option<HitKind> {
if !n.visible || !self.whole_instance && (n.locked || n.is_template()) || self.skips_guide(n) {
if !n.visible || !self.whole_instance && (n.locked || n.is_template()) || self.skips_guide(n) || !ArtWalk::take(1) {
return None;
}
let outline = outline || previews_off(n);
@@ -1298,17 +1316,120 @@ impl FindPart<'_> {
/// Outline, [`HitOptions::outline`]): its strokes, and the art that the live geometry effects of a
/// clipping path in it show past the art's box. `None` for an instance of a missing symbol, of a
/// symbol that holds an instance of itself or nests too deep ([`SymbolBoxes::art`]), and of art past
/// the 9-slice object limit ([`InstanceArt::of`]).
/// the 9-slice object limit ([`InstanceArt::of`]), when `p` is outside the instance's reach
/// ([`Node::reach_bounds_with`]), and once the click has visited as many objects of symbol art as
/// [`ArtWalk`] allows.
fn instance_art_hit(n: &Node, p: Point, opt: HitOptions, symbols: &SymbolBoxes) -> Option<HitKind> {
let NodeKind::SymbolInstance { symbol, .. } = &n.kind else { return None };
if opt.outline || symbols.art(symbol).is_none() {
if opt.outline || symbols.art(symbol).is_none() || !n.reach_bounds_with(symbols).is_some_and(|b| b.inflate(opt.tol, opt.tol).contains(p)) {
return None;
}
let _walk = ArtWalk::open();
if ArtWalk::used_up() {
return None;
}
let art = InstanceArt::of(symbols.document(), n)?;
#[cfg(test)]
ARTS_WALKED.with(|c| c.set(c.get() + 1));
let opt = HitOptions { clip_outlines: false, ..opt };
let _art_only = ArtOnly::set();
FindPart { art: &art, p, opt, symbols, whole_instance: true }.find(&art.art, &mut vec![], None, false)
}
thread_local! {
/// Whether a symbol instance's art is being walked outside the instance's box ([`ArtOnly`]).
static ART_ONLY: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}
/// While a guard lives, a symbol instance is hit ([`hit_leaf`]) and touched by a marquee
/// ([`marquee_with`]) only where its own art is, by the same rule at every level, and not anywhere
/// in its box. [`instance_art_hit`] and the marquee's walk of an instance's art hold one: they walk
/// the art outside the instance's box, where the box of an instance nested in the art can reach
/// past it over empty space.
struct ArtOnly {
was: bool,
}
impl ArtOnly {
fn set() -> Self {
Self { was: ART_ONLY.replace(true) }
}
fn on() -> bool {
ART_ONLY.get()
}
}
impl Drop for ArtOnly {
fn drop(&mut self) {
ART_ONLY.set(self.was);
}
}
thread_local! {
/// How many more objects of symbol art the click or marquee under way may visit ([`ArtWalk`]),
/// `None` outside one.
static ART_WALK_LEFT: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
}
#[cfg(test)]
thread_local! {
/// The number of objects [`ArtWalk::open`] allows on this thread in place of
/// [`crate::MAX_SYMBOL_NODES`].
static ART_WALK_LIMIT: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
/// How many symbol instances' art clicks and marquees walked on this thread.
static ARTS_WALKED: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
}
/// The objects of symbol art that one click or one marquee may visit: [`crate::MAX_SYMBOL_NODES`]
/// in all, the art of the instances nested in that art included, at every level. The first
/// [`ArtWalk::open`] of a click or a marquee sets the limit, and the walks opened while its guard
/// lives share it. Once the limit is used up, an instance is no longer hit or touched outside its
/// box; inside its box it still is.
struct ArtWalk {
outermost: bool,
}
impl ArtWalk {
fn open() -> Self {
let walk = Self { outermost: ART_WALK_LEFT.get().is_none() };
walk.restart();
walk
}
/// For the outermost guard, sets what is left back to the whole limit.
fn restart(&self) {
if self.outermost {
#[cfg(test)]
let limit = ART_WALK_LIMIT.get().unwrap_or(crate::MAX_SYMBOL_NODES);
#[cfg(not(test))]
let limit = crate::MAX_SYMBOL_NODES;
ART_WALK_LEFT.set(Some(limit));
}
}
/// Is nothing left?
fn used_up() -> bool {
ART_WALK_LEFT.get() == Some(0)
}
/// Takes `n` objects from what is left. `false` when fewer are left, and nothing is left after.
fn take(n: u64) -> bool {
let Some(left) = ART_WALK_LEFT.get() else { return true };
let rest = left.checked_sub(n);
ART_WALK_LEFT.set(Some(rest.unwrap_or(0)));
rest.is_some()
}
}
impl Drop for ArtWalk {
fn drop(&mut self) {
if self.outermost {
ART_WALK_LEFT.set(None);
}
}
}
/// Is `n` below a layer whose Preview is off, so that the canvas draws it in outline? As for clicks
/// ([`path_options`]), the Preview of the isolated container `scope` and of the layers around it
/// does not count.
@@ -1502,6 +1623,7 @@ fn previews_off(n: &Node) -> bool {
}
fn marquee_with(doc: &Document, r: Rect, scope: Option<NodeId>, leaves: bool, whole_compounds: bool, outline: bool) -> Vec<NodeId> {
let _walk = ArtWalk::open();
let mut out = Vec::new();
// The objects the marquee may touch ([`Top`]).
let tops: Vec<Top> = match scope.and_then(|s| doc.node(s)) {
@@ -1613,8 +1735,9 @@ fn marquee_with(doc: &Document, r: Rect, scope: Option<NodeId>, leaves: bool, wh
NodeKind::Compound { children, .. } => children.iter().any(|c| c.visible && touches(c, s, r, scope, whole_compounds, outline)),
NodeKind::Repeat(spec) => spec.expand(Some(s.document())).iter().any(|copy| touches(copy, s, r, scope, whole_compounds, outline)),
// A symbol instance is touched by its box, and outside it where the live geometry
// effects of a clipping path in its art show the art.
NodeKind::SymbolInstance { .. } => box_touches(n, s, r) || !outline && instance_art_touches(n, s, r, whole_compounds),
// effects of a clipping path in its art show the art. In the art of another instance
// walked outside that instance's box, it is touched only by its own art ([`ArtOnly`]).
NodeKind::SymbolInstance { .. } => !ArtOnly::on() && box_touches(n, s, r) || !outline && instance_art_touches(n, s, r, whole_compounds),
_ => box_touches(n, s, r),
}
}
@@ -1628,15 +1751,26 @@ fn marquee_with(doc: &Document, r: Rect, scope: Option<NodeId>, leaves: bool, wh
}
/// Does the marquee `r` touch the art of symbol instance `n` placed as the canvas draws it, as
/// it touches a group of that art? Only for art whose clipping paths have live geometry effects
/// ([`SymbolBoxes::art`]), and only when `r` meets the instance's reach
/// ([`Node::reach_bounds_with`]): elsewhere the art lies within the instance's box.
/// ([`SymbolBoxes::art`]), or for any art while [`ArtOnly`] is on, and only when `r` meets the
/// instance's reach ([`Node::reach_bounds_with`]): elsewhere the art lies within the instance's
/// box. Not once the marquee has placed as many objects of symbol art as [`ArtWalk`] allows.
fn instance_art_touches(n: &Node, s: &SymbolBoxes, r: Rect, whole_compounds: bool) -> bool {
let NodeKind::SymbolInstance { symbol, .. } = &n.kind else { return false };
let meets = |b: Rect| b.x0 <= r.x1 && r.x0 <= b.x1 && b.y0 <= r.y1 && r.y0 <= b.y1;
if s.art(symbol).is_none_or(|a| a.reshaped.is_none()) || !n.reach_bounds_with(s).is_some_and(meets) {
if s.art(symbol).is_none_or(|a| a.reshaped.is_none() && !ArtOnly::on()) || !n.reach_bounds_with(s).is_some_and(meets) {
return false;
}
let _walk = ArtWalk::open();
if ArtWalk::used_up() {
return false;
}
let Some(art) = InstanceArt::of(s.document(), n) else { return false };
if !ArtWalk::take(art.size()) {
return false;
}
#[cfg(test)]
ARTS_WALKED.with(|c| c.set(c.get() + 1));
let _art_only = ArtOnly::set();
art.place(&art.art, None).is_some_and(|placed| placed.visible && touches(&placed, s, r, None, whole_compounds, false))
}
fn collect_leaves(n: &Node, s: &SymbolBoxes, r: Rect, scope: Option<NodeId>, whole_compounds: bool, outline: bool, out: &mut Vec<NodeId>) {
+179
View File
@@ -1961,6 +1961,185 @@ fn a_symbol_instance_is_hit_on_the_stroke_of_its_art_past_its_box() {
assert_eq!(at(108.0, HitOptions { outline: true, ..HitOptions::default() }), None, "Outline view");
}
/// Outside a symbol instance's box, an instance nested in its art counts where its own art is, not
/// anywhere in its box: a click or a marquee on empty space in the nested box, where a moved
/// clipping path shows the art past the outer box, selects nothing, and on art drawn there it
/// selects the outer instance.
#[test]
fn a_nested_symbol_instances_box_past_the_outer_box_is_not_a_hit() {
set_clip_region(moved_clip_region);
// `Two` draws squares at the corners of 0..200 × 0..100, and with `middle` one at 140..160 ×
// 40..60. The outer symbol clips an instance of it to 0..100 × 0..100, which `test.move` moves
// to 100..200: the outer box is 0..100, the nested box 0..200.
let build = |middle: bool| {
let mut d = Document::new(600.0, 400.0);
let l = d.layers[0].id;
let square = |d: &mut Document, r: Rect| Arc::new(Node::path(d.alloc_id(), shapes::rectangle(r), Appearance::default_art()));
let mut squares = vec![square(&mut d, Rect::new(0.0, 0.0, 10.0, 10.0)), square(&mut d, Rect::new(190.0, 90.0, 200.0, 100.0))];
if middle {
squares.push(square(&mut d, Rect::new(140.0, 40.0, 160.0, 60.0)));
}
group_symbol(&mut d, "Two", squares);
let (mut mask, _) = half_clipped(&mut d);
mask.appearance.effects.push(test_move());
let nested = instance_of(&mut d, "Two");
let mut clip = Node::group(d.alloc_id(), vec![Arc::new(mask), nested]);
clip.set_clips(true);
group_symbol(&mut d, "Outer", vec![Arc::new(clip)]);
let inst = d.alloc_id();
d.insert(Some(l), 0, Node::new(inst, NodeKind::SymbolInstance { symbol: "Outer".into(), xf: vectorcraft_geom::Affine::IDENTITY })).unwrap();
(d, inst)
};
let at = |d: &Document, x: f64, y: f64| hit_test(d, Point::new(x, y), HitOptions::default()).map(|h| h.leaf);
let inside = Rect::new(145.0, 45.0, 155.0, 55.0);
let (d, inst) = build(false);
assert_eq!(at(&d, 150.0, 50.0), None, "empty space in the nested box");
assert!(marquee(&d, inside, None, false).is_empty(), "marquee on empty space in the nested box");
assert_eq!(at(&d, 195.0, 95.0), Some(inst), "on the nested art past the outer box");
assert_eq!(at(&d, 50.0, 50.0), Some(inst), "in the outer box");
let (d, inst) = build(true);
assert_eq!(at(&d, 150.0, 50.0), Some(inst), "on the nested art");
// Across the middle square's left edge.
assert_eq!(marquee(&d, Rect::new(135.0, 45.0, 145.0, 55.0), None, false), [inst], "marquee on the nested art");
}
/// Adds the symbol `Clipped`: a clip group whose clipping path's region `test.move` moves to
/// 100..200 × 0..100, past the 0..100 box, over a 0..200 × 0..100 rectangle shown when `shown`.
/// A click at (150, 50) hits an instance of it on the rectangle, or, with the rectangle hidden,
/// walks its art and finds nothing.
fn clipped_symbol(d: &mut Document, shown: bool) {
set_clip_region(moved_clip_region);
let (mut mask, mut art) = half_clipped(d);
mask.appearance.effects.push(test_move());
art.visible = shown;
let mut clip = Node::group(d.alloc_id(), vec![Arc::new(mask), Arc::new(art)]);
clip.set_clips(true);
let root = Node::group(d.alloc_id(), vec![Arc::new(clip)]);
d.symbols.push(crate::Symbol { dynamic: false, name: "Clipped".into(), art: Arc::new(root) });
}
/// A symbol named `name` whose art is a group of `members`.
fn group_symbol(d: &mut Document, name: &str, members: Vec<Arc<Node>>) {
let root = Node::group(d.alloc_id(), members);
d.symbols.push(crate::Symbol { dynamic: false, name: name.into(), art: Arc::new(root) });
}
/// An instance of the symbol named `symbol`, not moved.
fn instance_of(d: &mut Document, symbol: &str) -> Arc<Node> {
Arc::new(Node::new(d.alloc_id(), NodeKind::SymbolInstance { symbol: symbol.into(), xf: vectorcraft_geom::Affine::IDENTITY }))
}
/// 30 small squares at the origin, which a click at (150, 50) passes by.
fn far_squares(d: &mut Document) -> Vec<Arc<Node>> {
(0..30).map(|_| Arc::new(Node::path(d.alloc_id(), shapes::rectangle(Rect::new(0.0, 0.0, 1.0, 1.0)), Appearance::default_art()))).collect()
}
/// Runs `f` with [`ArtWalk`] allowing `limit` objects, and returns its result with how many
/// leaves [`hit_leaf`] tested and how many instances' art was walked meanwhile.
fn with_art_limit<R>(limit: u64, f: impl FnOnce() -> R) -> (R, usize, usize) {
ART_WALK_LIMIT.with(|c| c.set(Some(limit)));
LEAVES_TESTED.with(|c| c.set(0));
ARTS_WALKED.with(|c| c.set(0));
let r = f();
ART_WALK_LIMIT.with(|c| c.set(None));
(r, LEAVES_TESTED.with(std::cell::Cell::get), ARTS_WALKED.with(std::cell::Cell::get))
}
/// One click visits at most the art walk's limit of objects in a chain of nested symbol
/// instances, though each level's art alone is well within it. A click on the art of an
/// instance nested in another is still found.
#[test]
fn a_click_walks_a_chain_of_nested_symbol_instances_within_one_limit() {
// Level 0 is `Clipped`; each level above holds an instance of the level below and 30 squares:
// 32 objects to visit at each level.
let chain = |shown: bool| {
let mut d = Document::new(600.0, 400.0);
let l = d.layers[0].id;
clipped_symbol(&mut d, shown);
for level in 1..12 {
let below = if level == 1 { "Clipped".to_string() } else { format!("Level {}", level - 1) };
let mut members = vec![instance_of(&mut d, &below)];
members.extend(far_squares(&mut d));
group_symbol(&mut d, &format!("Level {level}"), members);
}
let inst = instance_of(&mut d, "Level 11");
let id = inst.id;
d.insert(Some(l), 0, Arc::unwrap_or_clone(inst)).unwrap();
(d, id)
};
let at = |d: &Document, x: f64| hit_test(d, Point::new(x, 50.0), HitOptions::default()).map(|h| h.leaf);
// Without one limit for the click, the miss walks the art of all 12 levels.
let (d, inst) = chain(false);
let (hit, _, arts) = with_art_limit(100, || at(&d, 150.0));
assert_eq!(hit, None);
assert!(arts <= 4, "{arts} levels walked");
assert_eq!(with_art_limit(100, || at(&d, 50.0)).0, Some(inst), "in the box");
// Past the limit, a click that the art would take finds only the box.
let (d, inst) = chain(true);
assert_eq!(with_art_limit(100, || at(&d, 150.0)).0, None, "past the limit");
assert_eq!(at(&d, 150.0), Some(inst), "within the limit");
// Two levels: a click on the inner instance's art is found within the limit.
let mut d = Document::new(600.0, 400.0);
let l = d.layers[0].id;
clipped_symbol(&mut d, true);
let inner = instance_of(&mut d, "Clipped");
group_symbol(&mut d, "Outer", vec![inner]);
let inst = instance_of(&mut d, "Outer");
let id = inst.id;
d.insert(Some(l), 0, Arc::unwrap_or_clone(inst)).unwrap();
assert_eq!(with_art_limit(100, || at(&d, 150.0)).0, Some(id), "nested");
}
/// One click, and one marquee, visit at most the art walk's limit of objects in art that repeats
/// one nested instance many times, though the art of each instance alone is well within it. A
/// click on one of the nested instances' art is still found.
#[test]
fn a_click_and_a_marquee_walk_repeated_nested_symbol_instances_within_one_limit() {
// 50 instances of `Clipped` in one symbol's art, its rectangle shown when `shown`.
let repeated = |shown: bool| {
let mut d = Document::new(600.0, 400.0);
let l = d.layers[0].id;
clipped_symbol(&mut d, shown);
let members = (0..50).map(|_| instance_of(&mut d, "Clipped")).collect();
group_symbol(&mut d, "Repeated", members);
let inst = instance_of(&mut d, "Repeated");
let id = inst.id;
d.insert(Some(l), 0, Arc::unwrap_or_clone(inst)).unwrap();
(d, id)
};
let (d, inst) = repeated(false);
// Without one limit for the click, the miss walks the art of all 51 instances. The outer art
// takes 1 object of the 100, and each nested instance with its art 4.
let (hit, _, arts) = with_art_limit(100, || hit_test(&d, Point::new(150.0, 50.0), HitOptions::default()).map(|h| h.leaf));
assert_eq!(hit, None);
assert!(arts <= 26, "{arts} instances' art walked");
// Select Behind over 60 instances of `Clipped` on the layer shares one limit too: each walk
// takes 3 objects of the 100.
let mut stack = Document::new(600.0, 400.0);
let l = stack.layers[0].id;
clipped_symbol(&mut stack, false);
for _ in 0..60 {
let inst = instance_of(&mut stack, "Clipped");
stack.insert(Some(l), 0, Arc::unwrap_or_clone(inst)).unwrap();
}
let (behind, _, arts) = with_art_limit(100, || objects_at(&stack, Point::new(150.0, 50.0), HitOptions::default(), None));
assert!(behind.is_empty());
assert!(arts <= 34, "Select Behind: {arts} instances' art walked");
// Without one limit for the marquee, it places the art of all 51 instances, inside the moved
// region (within each instance's reach and outside its box). Placing the outer art takes 51
// objects of the 100, and each nested art 4.
let inside = Rect::new(140.0, 40.0, 160.0, 60.0);
let (touched, _, arts) = with_art_limit(100, || marquee(&d, inside, None, false));
assert!(touched.is_empty());
assert!(arts <= 14, "{arts} instances' art placed");
assert_eq!(with_art_limit(100, || marquee(&d, Rect::new(40.0, 40.0, 60.0, 60.0), None, false)).0, [inst], "in the box");
// A click and a marquee on the nested instances' art are found within the limit.
let (d, inst) = repeated(true);
assert_eq!(with_art_limit(100, || hit_test(&d, Point::new(150.0, 50.0), HitOptions::default()).map(|h| h.leaf)).0, Some(inst), "click");
let edge = Rect::new(140.0, -5.0, 160.0, 5.0);
assert_eq!(with_art_limit(100, || marquee(&d, edge, None, false)).0, [inst], "marquee across the art's edge");
}
/// In a dynamic symbol's art that placing through its 9-slice guides could make more than
/// [`crate::MAX_SYMBOL_NODES`] objects of, Direct Selection picks and outlines nothing, as the
/// canvas draws nothing of it. At the art's natural size the same art is picked.