diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index f66a0eef0e..5ea7a2fc53 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -1676,7 +1676,7 @@ fn draw_pages( // Set again below by the page right-clicked: a right-click off the pages (between them, // beside one) anchors no page's menu, which then acts on the current page. It deselects the // comment, and Edit text's picture, too (below), as one on a page's empty part does. A - // selected Prepare field keeps its menu, off the pages as on them. + // selected Prepare field stays selected, and the menu is the page's there too. view.comments.context_at = None; view.menu_for = None; } diff --git a/crates/ui-egui/src/canvas/page_input.rs b/crates/ui-egui/src/canvas/page_input.rs index cffb7ffc9b..3cf1ae8ec2 100644 --- a/crates/ui-egui/src/canvas/page_input.rs +++ b/crates/ui-egui/src/canvas/page_input.rs @@ -462,6 +462,9 @@ impl PageHits<'_> { /// whatever the field tool. Links count for the tools that reach them (Select; the Link tool, /// below). Edit text's paragraphs have no menu, and give way to the picture under them (the /// user's ruling): with Edit text, a picture under a link or a paragraph takes the right-click. + /// Nor have added content or an open paragraph's frame: those give way to a picture under them, + /// and to nothing else. (Where objects are selected together, the page view unselects the picture + /// a right-click selects, and the menu is the page's.) /// A highlight over a link is the highlight's ([`comments::context_hit`]). /// The Link tool edits links and reaches no comment: on a document it can change, its /// right-click is what its press there would take (`under`, the hits at `p`), when that is @@ -473,11 +476,18 @@ impl PageHits<'_> { { return Some(press.owner); } - let reached = self + let reached: Vec<_> = self .hits(view, p, true, None) .into_iter() - .filter(|h| (h.layer != Layer::Link || h.layer.reached_by(self.tool)) && !matches!(h.owner, Owner::Paragraph { .. })); - top(reached, QuickTool::Select).map(|h| h.owner) + .filter(|h| (h.layer != Layer::Link || h.layer.reached_by(self.tool)) && !matches!(h.owner, Owner::Paragraph { .. })) + .collect(); + let on_top = top(reached.iter().cloned(), QuickTool::Select)?.owner; + if self.tool == QuickTool::EditText && matches!(on_top, Owner::Added { .. } | Owner::Objects { part: crate::page_router::ObjectsPart::Frame }) + { + let pictures = reached.into_iter().filter(|h| matches!(h.owner, Owner::Picture { .. })); + return Some(top(pictures, QuickTool::Select).map_or(on_top, |h| h.owner)); + } + Some(on_top) } /// The hits at `p`, bottom up: links, comments; Edit text's pictures under its paragraphs (text diff --git a/crates/ui-egui/tests/it/editing.rs b/crates/ui-egui/tests/it/editing.rs index 2d231ab359..0ec0c6f781 100644 --- a/crates/ui-egui/tests/it/editing.rs +++ b/crates/ui-egui/tests/it/editing.rs @@ -7336,6 +7336,47 @@ fn a_right_click_on_text_over_a_picture_selects_the_picture() { assert_eq!(selected, Some((0, 0)), "the picture under the text is selected for its menu"); } +/// With Edit text, a right-click on added text over a picture is the picture's, which is selected +/// for its menu: added text has no menu of its own. It unselected the picture and opened the page's. +#[test] +fn a_right_click_on_added_text_over_a_picture_selects_the_picture() { + let mut h = picture_pages(false); + h.state_mut().views[0].image_selection = None; + assert!(h.state_mut().apply_edit(pdfcraft_engine::Edit::AddText { + page: 0, + text: pdfcraft_engine::AddedText { rect: [60.0, 190.0, 150.0, 205.0], text: "Added".into(), ..Default::default() }, + })); + h.run_steps(3); + let on_it = page_at(&h, 0, 100.0, 198.0); + right_click_at(&mut h, on_it); + let selected = h.state().views[0].image_selection.as_ref().map(|s| (s.page, s.index)); + assert_eq!(selected, Some((0, 0)), "the picture under the added text is selected for its menu"); + assert!(h.query_by_label("Flip horizontal").is_some(), "its menu opens"); +} + +/// With Edit text, a right-click on the frame of a paragraph open for typing over a picture is the +/// picture's too. It unselected the picture and opened the page's menu. +#[test] +fn a_right_click_on_an_open_paragraphs_frame_over_a_picture_selects_the_picture() { + let mut h = picture_pages(false); + h.state_mut().views[0].image_selection = None; + h.run_steps(2); + // "Overlay" lies on the picture, at x 40, y 220. + let overlay = page_at(&h, 0, 50.0, 224.0); + slipping_click(&mut h, overlay, overlay); + assert!(h.state().views[0].line_editor.is_some(), "the paragraph is open for typing"); + let id = h.state().views[0].id; + let blocks = h.state().session.get(id).unwrap().text_blocks(0); + let block = + blocks.iter().find(|b| b.rect[0] < 50.0 && 50.0 < b.rect[2] && b.rect[1] < 224.0 && 224.0 < b.rect[3]).expect("the paragraph opened").clone(); + // Its frame runs just outside its box. + let frame = page_at(&h, 0, block.rect[0] as f32, ((block.rect[1] + block.rect[3]) / 2.0) as f32) - egui::vec2(3.0, 0.0); + right_click_at(&mut h, frame); + let selected = h.state().views[0].image_selection.as_ref().map(|s| (s.page, s.index)); + assert_eq!(selected, Some((0, 0)), "the picture under the frame is selected for its menu"); + assert!(h.query_by_label("Flip horizontal").is_some(), "its menu opens"); +} + /// In crop mode, a right-click on another picture selects it for its menu, as without crop mode. It /// went to crop mode's whole-page hit, which unselected every picture. #[test] diff --git a/crates/ui-egui/tests/it/pointer.rs b/crates/ui-egui/tests/it/pointer.rs index 3bb43e56d6..71879d6e5e 100644 --- a/crates/ui-egui/tests/it/pointer.rs +++ b/crates/ui-egui/tests/it/pointer.rs @@ -1579,6 +1579,22 @@ fn a_right_click_with_edit_text_on_a_paragraph_under_a_comment_selects_the_comme assert_eq!(h.state().views[0].comments.selected, None, "the paragraph beside the comment leaves it unselected"); } +/// With Edit text, a right-click on added text over a comment selects no comment: what has no menu +/// gives way to a picture under it and to nothing else, as before. +#[test] +fn a_right_click_with_edit_text_on_added_text_over_a_comment_selects_no_comment() { + let mut h = open(comment_on_text_pdf(), "150", false); + assert!(h.state_mut().execute("edit.edit_text")); + assert!(h.state_mut().apply_edit(pdfcraft_engine::Edit::AddText { + page: 0, + text: pdfcraft_engine::AddedText { rect: [110.0, 296.0, 190.0, 311.0], text: "Added".into(), ..Default::default() }, + })); + h.run_steps(4); + let on_added = at(&h, 150.0, 303.0); + right_click(&mut h, on_added); + assert_eq!(h.state().views[0].comments.selected, None, "the comment under the added text is not selected"); +} + /// With a Prepare field tool a right-click is the tool's, as before: a comment under it is not /// selected. #[test]