diff --git a/crates/doc/src/edit.rs b/crates/doc/src/edit.rs index 4353ecab9..bd8e82bb2 100644 --- a/crates/doc/src/edit.rs +++ b/crates/doc/src/edit.rs @@ -632,6 +632,22 @@ impl Document { None } + /// Hit test on a parent spread (master page): like [`Self::hit_item`], but also skips locked + /// items. Items on hidden or locked layers can't be picked there either. + pub fn hit_parent_item(&self, sr: SpreadRef, p: Point, tol: f64) -> Option { + let sp = self.spread(sr)?; + let mut items: Vec<&Arc> = sp.items.iter().collect(); + items.sort_by_key(|i| self.layer_rank(i.layer)); + items + .into_iter() + .rev() + .find(|it| { + let layer = self.layer(it.layer); + !it.locked && !it.hidden && !layer.is_some_and(|l| !l.visible || l.locked) && item_hit(it, p, tol) + }) + .map(|it| it.id) + } + /// The first item on a document or parent spread whose geometry isn't finite /// ([`Item::geometry_is_finite`]). pub fn non_finite_item(&self) -> Option { diff --git a/crates/doc/src/tests.rs b/crates/doc/src/tests.rs index 57660d7c4..51d4d8dd0 100644 --- a/crates/doc/src/tests.rs +++ b/crates/doc/src/tests.rs @@ -235,6 +235,20 @@ fn hit_testing_respects_layers() { assert_eq!(d.hit_item(0, designcraft_geom::Point::new(50.0, 50.0), 2.0), None); } +#[test] +fn parent_hit_testing_respects_layers() { + let mut d = doc(); + let lid = d.default_layer(); + let (a, _) = d.add_text_frame(SpreadRef::Parent(0), Rect::new(10.0, 10.0, 100.0, 100.0), lid, "x", ParaFormat::default()).unwrap(); + let at = designcraft_geom::Point::new(50.0, 50.0); + assert_eq!(d.hit_parent_item(SpreadRef::Parent(0), at, 2.0), Some(a)); + d.layer_mut(lid).unwrap().visible = false; + assert_eq!(d.hit_parent_item(SpreadRef::Parent(0), at, 2.0), None); + d.layer_mut(lid).unwrap().visible = true; + d.layer_mut(lid).unwrap().locked = true; + assert_eq!(d.hit_parent_item(SpreadRef::Parent(0), at, 2.0), None); +} + #[test] fn page_numbers_saturate_at_the_largest_start() { // A start number from a file or an old command: page 2 overflowed (a panic in debug builds). diff --git a/crates/tools/src/lib.rs b/crates/tools/src/lib.rs index 57872cff9..aec2c2349 100644 --- a/crates/tools/src/lib.rs +++ b/crates/tools/src/lib.rs @@ -300,8 +300,7 @@ impl ToolContext<'_> { } fn hit_parent(&self, sr: SpreadRef, sp: Point) -> Option<(SpreadRef, ItemId)> { - let s = self.doc.spread(sr)?; - s.items.iter().rev().find(|it| !it.locked && designcraft_doc::edit_hit(it, sp, self.tol(4.0))).map(|it| (sr, it.id)) + self.doc.hit_parent_item(sr, sp, self.tol(4.0)).map(|id| (sr, id)) } /// Spread-space → canvas transform of the spread holding item `id`. diff --git a/crates/tools/src/tests.rs b/crates/tools/src/tests.rs index 4b7c893fb..e1515a6a9 100644 --- a/crates/tools/src/tests.rs +++ b/crates/tools/src/tests.rs @@ -220,6 +220,25 @@ fn parent_hit_reaches_base_parent_items_topmost_first() { assert_eq!(hit(&d, 120.0, 120.0), None); } +/// While editing parent pages, a click doesn't pick items on a hidden or locked layer (#2221). +#[test] +fn parent_mode_hit_skips_hidden_and_locked_layers() { + let mut d = Document::new(&NewDocument { pages: 1, facing_pages: false, ..Default::default() }); + let lid = d.default_layer(); + let (id, _) = d.add_text_frame(SpreadRef::Parent(0), Rect::new(100.0, 100.0, 200.0, 200.0), lid, "a", ParaFormat::default()).unwrap(); + let hit = |d: &Document| { + let (s, c, l) = (Selection::default(), Cache::new(), CanvasLayout::new(d, true)); + let p = l.to_canvas(SpreadRef::Parent(0), Point::new(150.0, 150.0)); + ctx(d, &s, &c, &l).hit(p) + }; + assert_eq!(hit(&d), Some((SpreadRef::Parent(0), id))); + d.layer_mut(lid).unwrap().visible = false; + assert_eq!(hit(&d), None, "hidden layer"); + d.layer_mut(lid).unwrap().visible = true; + d.layer_mut(lid).unwrap().locked = true; + assert_eq!(hit(&d), None, "locked layer"); +} + #[test] fn gradient_tool_drag_sets_the_vector() { let mut d = Document::new(&NewDocument::default()); diff --git a/crates/ui-egui/src/canvas/input.rs b/crates/ui-egui/src/canvas/input.rs index 21f269e03..e1db50375 100644 --- a/crates/ui-egui/src/canvas/input.rs +++ b/crates/ui-egui/src/canvas/input.rs @@ -164,7 +164,7 @@ fn item_at(app: &DesignApp, p: Point, zoom: f64) -> Option<(designcraft_doc::Ite let tol = 4.0 / zoom.max(1e-6); let id = match sr { SpreadRef::Doc(si) => st.doc.hit_item(si, sp, tol)?, - _ => st.doc.spread(sr)?.items.iter().rev().find(|it| !it.locked && designcraft_doc::edit_hit(it, sp, tol))?.id, + SpreadRef::Parent(_) => st.doc.hit_parent_item(sr, sp, tol)?, }; Some((id, sp)) }