From e725c4920cdd6ddefa2eaca0588e08ee04e44abc Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 31 Oct 2026 22:26:08 -0700 Subject: [PATCH] Tests: nested clip groups in isolation mode and Direct Selection drags - isolation_reaches_hidden_art_only_for_the_isolated_clip_group: with the outer clip group isolated, clicks and marquees take the art its own mask hides, while an inner clip group's mask still clips that group's art; isolating the inner group or its art reaches the inner hidden art. - isolation_lets_direct_selection_take_the_art_a_clip_group_hides now continues the press with a drag on the same tool and checks the Move step and its preview, for Direct Selection and Group Selection. --- crates/doc/src/hit/tests.rs | 50 ++++++++++++++++++++++++++++++++ crates/tools/src/direct/tests.rs | 13 +++++++-- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/crates/doc/src/hit/tests.rs b/crates/doc/src/hit/tests.rs index 2e2cd6944..8cc3dd71f 100644 --- a/crates/doc/src/hit/tests.rs +++ b/crates/doc/src/hit/tests.rs @@ -533,6 +533,56 @@ fn isolation_reaches_the_art_a_clipping_mask_hides() { } } +/// #3748, nested clip groups: with the outer clip group isolated, a click or a marquee takes +/// the art its own mask hides, while the mask of a clip group inside it still limits that +/// group's art. With the inner clip group isolated, or its art, the inner mask does not. +#[test] +fn isolation_reaches_hidden_art_only_for_the_isolated_clip_group() { + let mut d = Document::new(300.0, 200.0); + let l = d.layers[0].id; + let (outer, outer_clip, outer_art, inner, inner_clip, inner_art) = + (d.alloc_id(), d.alloc_id(), d.alloc_id(), d.alloc_id(), d.alloc_id(), d.alloc_id()); + let mask = |id, r| { + let mut path = Node::path(id, shapes::rectangle(r), Appearance::basic(Paint::None, Paint::None, 0.0)); + if let NodeKind::Path { clipping, .. } = &mut path.kind { + *clipping = true; + } + Arc::new(path) + }; + let art = |id, r| Arc::new(Node::path(id, shapes::rectangle(r), Appearance::default_art())); + let inner_group = Node::new( + inner, + NodeKind::Group { + children: vec![mask(inner_clip, Rect::new(60.0, 60.0, 120.0, 120.0)), art(inner_art, Rect::new(40.0, 40.0, 160.0, 160.0))], + clip: true, + }, + ); + let children = + vec![mask(outer_clip, Rect::new(20.0, 20.0, 180.0, 180.0)), art(outer_art, Rect::new(190.0, 20.0, 260.0, 180.0)), Arc::new(inner_group)]; + d.insert(Some(l), 0, Node::new(outer, NodeKind::Group { children, clip: true })).unwrap(); + // `outer_hidden` is outside the outer mask, on the outer group's own art; `inner_hidden` is + // inside the outer mask and outside the inner one, on the inner group's art. + let (outer_hidden, inner_hidden, shown) = (Point::new(220.0, 100.0), Point::new(150.0, 100.0), Point::new(90.0, 90.0)); + let leaf = |p, scope| hit_test(&d, p, HitOptions { scope, ..SELECTING }).map(|h| h.leaf); + assert_eq!((leaf(outer_hidden, None), leaf(inner_hidden, None)), (None, None), "not isolated"); + assert_eq!(leaf(outer_hidden, Some(outer)), Some(outer_art), "outer isolated: its own hidden art"); + assert_eq!(leaf(inner_hidden, Some(outer)), None, "outer isolated: the inner mask still clips"); + let h = hit_test(&d, shown, HitOptions { scope: Some(outer), ..SELECTING }); + assert_eq!(h.map(|h| (h.leaf, h.top_object(Some(outer)))), Some((inner_art, inner)), "outer isolated: shown art"); + assert_eq!(leaf(inner_hidden, Some(inner)), Some(inner_art), "inner isolated"); + assert_eq!(leaf(outer_hidden, Some(inner)), None, "inner isolated: the outer group's art"); + assert_eq!(leaf(inner_hidden, Some(inner_art)), Some(inner_art), "the inner art isolated"); + // These marquees cross the right edge of the outer art (outside the outer mask) and of the + // inner art (inside the outer mask, outside the inner one). + let (outer_edge, inner_edge) = (Rect::new(255.0, 95.0, 265.0, 105.0), Rect::new(155.0, 95.0, 165.0, 105.0)); + assert_eq!(marquee(&d, outer_edge, Some(outer), false), vec![outer_art]); + assert!(marquee(&d, inner_edge, Some(outer), false).is_empty()); + assert!(marquee(&d, inner_edge, Some(outer), true).is_empty()); + assert_eq!(marquee(&d, inner_edge, Some(inner), false), vec![inner_art]); + assert!(marquee(&d, inner_edge, None, false).is_empty()); + assert!(marquee(&d, outer_edge, None, false).is_empty()); +} + /// Art in a sublayer is an object of its own: a click or a marquee takes it, not the /// sublayer; hidden, locked and template sublayers block both. #[test] diff --git a/crates/tools/src/direct/tests.rs b/crates/tools/src/direct/tests.rs index d61a8e538..718983d96 100644 --- a/crates/tools/src/direct/tests.rs +++ b/crates/tools/src/direct/tests.rs @@ -129,16 +129,25 @@ fn a_press_on_a_clipping_path_takes_its_segment_over_the_art() { } /// #3748: in isolation mode Direct Selection and Group Selection take the art that the -/// isolated clip group hides outside its mask; outside isolation mode a press there does not. +/// isolated clip group hides outside its mask, and a drag from there moves it; outside +/// isolation mode a press there does not take it. #[test] fn isolation_lets_direct_selection_take_the_art_a_clip_group_hides() { let (d, g, _, art) = doc_with_masked_square(); let (s, p) = (Selection::default(), paint()); + let mut picked = Selection::default(); + picked.add(art); let names = |actions: &[Action]| actions.iter().any(|a| matches!(a, Action::Exec(_, v) if v["id"] == json!(art.0) || v["ids"] == json!([art.0]))); for group in [false, true] { let isolated = ToolContext { isolation: Some(g), ..cx(&d, &s, &p) }; - let a = DirectSelectionTool::new(group).pointer(&isolated, &PointerEvent::new(PointerKind::Down, 530.0, 450.0)); + let mut t = DirectSelectionTool::new(group); + let a = t.pointer(&isolated, &PointerEvent::new(PointerKind::Down, 530.0, 450.0)); assert!(names(&a), "group {group}: {a:?}"); + // The press selected the art: the drag that follows moves it. + let isolated = ToolContext { isolation: Some(g), ..cx(&d, &picked, &p) }; + let a = t.pointer(&isolated, &PointerEvent::new(PointerKind::Drag, 510.0, 470.0)); + assert!(a.iter().any(|a| matches!(a, Action::Begin(n) if n == "Move")), "group {group}: {a:?}"); + assert!(a.iter().any(|a| matches!(a, Action::Preview(..))), "group {group}: {a:?}"); let a = DirectSelectionTool::new(group).pointer(&cx(&d, &s, &p), &PointerEvent::new(PointerKind::Down, 530.0, 450.0)); assert!(!names(&a), "group {group}, not isolated: {a:?}"); }