diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 136522d6b3..295bb91179 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -182,21 +182,7 @@ impl ImageObb { let v_axis = (c / h_v, d / h_v); let is_rotated = b.abs() > 1e-4 || c.abs() > 1e-4; - Self { - s00, - s10, - s11, - s01, - s_bc, - s_rc, - s_tc, - s_lc, - s_rot, - screen_center, - u_axis, - v_axis, - is_rotated, - } + Self { s00, s10, s11, s01, s_bc, s_rc, s_tc, s_lc, s_rot, screen_center, u_axis, v_axis, is_rotated } } pub fn from_image(im: &pdfcraft_engine::PageImage, xf: &PageXform, info: &DocInfo, page: usize) -> Self { @@ -731,8 +717,8 @@ pub enum ViewAction { /// Default page right-click menu actions. #[derive(Clone, Debug, PartialEq)] pub(crate) enum PageMenuAction { - RotateCCW(usize), - RotateCW(usize), + RotateCCW, + RotateCW, Print, SelectTool, HandTool, @@ -740,7 +726,7 @@ pub(crate) enum PageMenuAction { Copy, Paste(usize, [f64; 2]), AddComment(usize, [f64; 2]), - AddBookmark(usize), + AddBookmark, TakeSnapshot, EditPdf, AddText, @@ -763,100 +749,66 @@ pub(crate) struct PageMenuPerms { } pub(crate) fn default_page_context_menu(ui: &mut egui::Ui, page: usize, at: [f64; 2], perms: PageMenuPerms, action: &mut Option) { + use PageMenuAction as A; + // Takes `choice` when `entry` was clicked, and closes the menu. + let mut pick = |entry: egui::Response, ui: &mut egui::Ui, choice: PageMenuAction| { + if entry.clicked() { + *action = Some(choice); + ui.close(); + } + }; + let icon = |ui: &mut egui::Ui, name: &str, tip: &str| crate::icons::button(ui, name, 24.0, false, tip); // Top horizontal icon bar: Rotate CCW, Print, Select tool, Hand tool, Marquee Zoom ui.horizontal(|ui| { ui.spacing_mut().item_spacing.x = 4.0; - if crate::icons::button(ui, "rotate-ccw", 24.0, false, tl!("Rotate view counterclockwise")).clicked() { - *action = Some(PageMenuAction::RotateCCW(page)); - ui.close(); - } - ui.add_enabled_ui(perms.can_print, |ui| { - if crate::icons::button(ui, "printer", 24.0, false, tl!("Print")).clicked() { - *action = Some(PageMenuAction::Print); - ui.close(); - } - }); - if crate::icons::button(ui, "mouse-pointer-2", 24.0, false, tl!("Select Tool")).clicked() { - *action = Some(PageMenuAction::SelectTool); - ui.close(); - } - if crate::icons::button(ui, "hand", 24.0, false, tl!("Hand Tool")).clicked() { - *action = Some(PageMenuAction::HandTool); - ui.close(); - } - if crate::icons::button(ui, "zoom-in", 24.0, false, tl!("Marquee Zoom")).clicked() { - *action = Some(PageMenuAction::MarqueeZoom); - ui.close(); - } + pick(icon(ui, "rotate-ccw", tl!("Rotate view counterclockwise")), ui, A::RotateCCW); + ui.add_enabled_ui(perms.can_print, |ui| pick(icon(ui, "printer", tl!("Print")), ui, A::Print)); + pick(icon(ui, "mouse-pointer-2", tl!("Select Tool")), ui, A::SelectTool); + pick(icon(ui, "hand", tl!("Hand Tool")), ui, A::HandTool); + pick(icon(ui, "zoom-in", tl!("Marquee Zoom")), ui, A::MarqueeZoom); }); ui.separator(); - if ui.add_enabled(perms.can_copy, egui::Button::new(tl!("Copy")).shortcut_text("Ctrl+C")).clicked() { - *action = Some(PageMenuAction::Copy); - ui.close(); - } - if ui.add_enabled(perms.can_paste, egui::Button::new(tl!("Paste")).shortcut_text("Ctrl+V")).clicked() { - *action = Some(PageMenuAction::Paste(page, at)); - ui.close(); - } + pick( + ui.add_enabled( + perms.can_copy, + egui::Button::new(tl!("Copy")).shortcut_text(crate::commands::shortcut_label(ui.ctx(), crate::commands::COPY)), + ), + ui, + A::Copy, + ); + pick( + ui.add_enabled( + perms.can_paste, + egui::Button::new(tl!("Paste")).shortcut_text(crate::commands::shortcut_label(ui.ctx(), pdfcraft_engine::commands::Shortcut::cmd("V"))), + ), + ui, + A::Paste(page, at), + ); ui.separator(); - if ui.add_enabled(perms.can_annotate, egui::Button::new(tl!("Add comment"))).clicked() { - *action = Some(PageMenuAction::AddComment(page, at)); - ui.close(); - } - if perms.has_annotations && ui.add_enabled(perms.can_modify, egui::Button::new(tl!("Flatten comments"))).clicked() { - *action = Some(PageMenuAction::FlattenComments); - ui.close(); - } - if ui.add_enabled(perms.can_assemble, egui::Button::new(tl!("Add bookmark"))).clicked() { - *action = Some(PageMenuAction::AddBookmark(page)); - ui.close(); - } - if ui.add_enabled(perms.can_copy, egui::Button::new(tl!("Take a snapshot"))).clicked() { - *action = Some(PageMenuAction::TakeSnapshot); - ui.close(); + pick(ui.add_enabled(perms.can_annotate, egui::Button::new(tl!("Add comment"))), ui, A::AddComment(page, at)); + if perms.has_annotations { + pick(ui.add_enabled(perms.can_modify, egui::Button::new(tl!("Flatten comments"))), ui, A::FlattenComments); } + pick(ui.add_enabled(perms.can_assemble, egui::Button::new(tl!("Add bookmark"))), ui, A::AddBookmark); + pick(ui.add_enabled(perms.can_copy, egui::Button::new(tl!("Take a snapshot"))), ui, A::TakeSnapshot); ui.separator(); - if ui.add_enabled(perms.can_modify, egui::Button::new(egui::RichText::new(tl!("Edit this PDF")).strong())).clicked() { - *action = Some(PageMenuAction::EditPdf); - ui.close(); - } - if ui.add_enabled(perms.can_modify, egui::Button::new(tl!("Add text"))).clicked() { - *action = Some(PageMenuAction::AddText); - ui.close(); - } - if ui.add_enabled(perms.can_modify, egui::Button::new(tl!("Add image"))).clicked() { - *action = Some(PageMenuAction::AddImage); - ui.close(); - } - if ui.add_enabled(perms.can_assemble, egui::Button::new(tl!("Delete page"))).clicked() { - *action = Some(PageMenuAction::DeletePage(page)); - ui.close(); - } - if ui.add_enabled(perms.can_copy, egui::Button::new(tl!("Export this PDF"))).clicked() { - *action = Some(PageMenuAction::ExportPdf); - ui.close(); - } + pick(ui.add_enabled(perms.can_modify, egui::Button::new(egui::RichText::new(tl!("Edit this PDF")).strong())), ui, A::EditPdf); + pick(ui.add_enabled(perms.can_modify, egui::Button::new(tl!("Add text"))), ui, A::AddText); + pick(ui.add_enabled(perms.can_modify, egui::Button::new(tl!("Add image"))), ui, A::AddImage); + pick(ui.add_enabled(perms.can_assemble, egui::Button::new(tl!("Delete page"))), ui, A::DeletePage(page)); + pick(ui.add_enabled(perms.can_copy, egui::Button::new(tl!("Export this PDF"))), ui, A::ExportPdf); ui.separator(); - if ui.button(tl!("Find text or tools")).clicked() { - *action = Some(PageMenuAction::Find); - ui.close(); - } - if ui.button(tl!("Rotate view clockwise")).clicked() { - *action = Some(PageMenuAction::RotateCW(page)); - ui.close(); - } - if ui.button(tl!("Document properties…")).clicked() { - *action = Some(PageMenuAction::DocProperties); - ui.close(); - } + pick(ui.button(tl!("Find text or tools")), ui, A::Find); + pick(ui.button(tl!("Rotate view clockwise")), ui, A::RotateCW); + pick(ui.button(tl!("Document properties…")), ui, A::DocProperties); } impl DocView { @@ -3951,17 +3903,6 @@ fn apply_frame_requests(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui, } } } - Some(crate::edit_text_ui::ImageAction::Crop) => { - let doc_id = app.views[index].id; - if let Some(s) = app.views[index].image_selection.as_mut() - && let Some(d) = app.session.get(doc_id) - { - let images = d.page_images(s.page); - if let Some(im) = images.get(s.index) { - s.crop = Some(crate::edit_text_ui::CropMode::new(im.visible_rect.unwrap_or(im.rect))); - } - } - } None => {} } match page_action { @@ -3980,12 +3921,10 @@ fn apply_frame_requests(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui, Some(PageMenuAction::Paste(page, at)) => { app.paste_image_at(page, at); } - Some(PageMenuAction::RotateCCW(page)) => { - let _ = page; + Some(PageMenuAction::RotateCCW) => { app.views[index].rotate_view(false); } - Some(PageMenuAction::RotateCW(page)) => { - let _ = page; + Some(PageMenuAction::RotateCW) => { app.views[index].rotate_view(true); } Some(PageMenuAction::Print) => { @@ -4008,7 +3947,7 @@ fn apply_frame_requests(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui, app.views[index].comments.selected = None; app.views[index].pending_edit = Some(pdfcraft_engine::Edit::Flatten { comments: true, fields: false }); } - Some(PageMenuAction::AddBookmark(_page)) => { + Some(PageMenuAction::AddBookmark) => { app.bookmark_action(crate::panels::BmAction::New); } Some(PageMenuAction::TakeSnapshot) => { diff --git a/crates/ui-egui/src/tools/edit_text_ui/image.rs b/crates/ui-egui/src/tools/edit_text_ui/image.rs index baff5ba5dd..a4edf00aad 100644 --- a/crates/ui-egui/src/tools/edit_text_ui/image.rs +++ b/crates/ui-egui/src/tools/edit_text_ui/image.rs @@ -92,6 +92,11 @@ pub struct ImageSelection { } impl ImageSelection { + /// Select a page image without an active gesture. + pub fn at(page: usize, index: usize) -> Self { + Self { page, index, drag: None, crop: None, crop_mode: false, crop_rect: None, turning: false, settled: None } + } + /// Cancel an in-progress text, rotation or crop drag and restore the crop it was previewing. pub(crate) fn cancel_pointer_gesture(&mut self) { self.drag = None; @@ -489,7 +494,6 @@ pub enum ImageAction { Paste(usize, Option<[f64; 2]>), DrawFreehand, AddComment(usize, usize), - Crop, } /// Where the rotate knob sits, and the edge point its stem starts from: above the middle of the @@ -1051,8 +1055,7 @@ pub(crate) fn image_input(cx: &ImageCx<'_>, images: &[pdfcraft_engine::PageImage _ => (resp.clicked() || resp.secondary_clicked()).then_some(hit), }; if let Some(index) = clicked { - view.image_selection = - Some(ImageSelection { page, index, drag: None, crop: None, crop_mode: false, crop_rect: None, turning: false, settled: None }); + view.image_selection = Some(ImageSelection::at(page, index)); } if resp.secondary_clicked() { ui.ctx().data_mut(|d| d.insert_persisted(menu_key, true)); @@ -1076,133 +1079,114 @@ pub(crate) fn image_context_menu( use pdfcraft_engine::ImageEdit as E; let raster = !img.is_form; - let close = |ui: &mut egui::Ui| { - ui.ctx().data_mut(|d| d.insert_persisted(image_menu_id(), false)); - egui::Popup::close_all(ui.ctx()); - ui.close(); + // Whether `entry` was clicked; a click closes the menu. + let picked = |ui: &mut egui::Ui, entry: egui::Response| { + let clicked = entry.clicked(); + if clicked { + ui.ctx().data_mut(|d| d.insert_persisted(image_menu_id(), false)); + egui::Popup::close_all(ui.ctx()); + ui.close(); + } + clicked }; + let item = |ui: &mut egui::Ui, enabled: bool, label: &str| { + let entry = ui.add_enabled(enabled, egui::Button::new(label)); + picked(ui, entry) + }; + let edit = |change| Some(Edit::EditPageImage { page, index: hit, change }); // Top horizontal icon bar: Comment, Crop, Rotate CCW, Rotate CW ui.horizontal(|ui| { ui.spacing_mut().item_spacing.x = 4.0; - if crate::icons::button(ui, "message-square", 24.0, false, tl!("Add a sticky note here")).clicked() { + let icon = |ui: &mut egui::Ui, name: &str, tip: &str| { + let entry = crate::icons::button(ui, name, 24.0, false, tip); + picked(ui, entry) + }; + if icon(ui, "message-square", tl!("Add a sticky note here")) { *action = Some(ImageAction::AddComment(page, hit)); - close(ui); } - if raster && crate::icons::button(ui, "crop", 24.0, false, tl!("Crop")).clicked() { - *action = Some(ImageAction::Crop); - if let Some(s) = view.image_selection.as_mut() { - let rect = img.visible_rect.unwrap_or(img.rect); - s.crop = Some(CropMode::new(rect)); - } - close(ui); + if raster + && icon(ui, "crop", tl!("Crop")) + && let Some(s) = view.image_selection.as_mut() + { + s.crop = Some(CropMode::new(img.visible_rect.unwrap_or(img.rect))); } - if crate::icons::button(ui, "rotate-ccw", 24.0, false, tl!("Rotate Counterclockwise")).clicked() { - view.pending_edit = Some(Edit::EditPageImage { page, index: hit, change: E::Rotate(3) }); - close(ui); + if icon(ui, "rotate-ccw", tl!("Rotate Counterclockwise")) { + view.pending_edit = edit(E::Rotate(3)); } - if crate::icons::button(ui, "rotate-cw", 24.0, false, tl!("Rotate Clockwise")).clicked() { - view.pending_edit = Some(Edit::EditPageImage { page, index: hit, change: E::Rotate(1) }); - close(ui); + if icon(ui, "rotate-cw", tl!("Rotate Clockwise")) { + view.pending_edit = edit(E::Rotate(1)); } }); ui.separator(); - if ui.button(tl!("Cut Image")).clicked() { + if item(ui, true, tl!("Cut Image")) { *action = Some(ImageAction::Cut(page, hit)); - close(ui); } - if ui.button(tl!("Copy Image")).clicked() { + if item(ui, true, tl!("Copy Image")) { *action = Some(ImageAction::Copy(page, hit)); - close(ui); } - if ui.add_enabled(can_paste, egui::Button::new(tl!("Paste"))).clicked() { + if item(ui, can_paste, tl!("Paste")) { *action = Some(ImageAction::Paste(page, at)); - close(ui); } ui.separator(); let is_cropped = img.visible_rect.is_some() && img.visible_rect != Some(img.rect); - if raster && ui.add_enabled(is_cropped, egui::Button::new(tl!("Remove crop"))).clicked() { - view.pending_edit = Some(Edit::EditPageImage { page, index: hit, change: E::RemoveCrop }); - close(ui); + if raster && item(ui, is_cropped, tl!("Remove crop")) { + view.pending_edit = edit(E::RemoveCrop); } - - if ui.button(tl!("Flip horizontal")).clicked() { - view.pending_edit = Some(Edit::EditPageImage { page, index: hit, change: E::Flip { horizontal: true } }); - close(ui); + if item(ui, true, tl!("Flip horizontal")) { + view.pending_edit = edit(E::Flip { horizontal: true }); } - if ui.button(tl!("Flip vertical")).clicked() { - view.pending_edit = Some(Edit::EditPageImage { page, index: hit, change: E::Flip { horizontal: false } }); - close(ui); + if item(ui, true, tl!("Flip vertical")) { + view.pending_edit = edit(E::Flip { horizontal: false }); } ui.menu_button(tl!("Arrange image"), |ui| { - if ui.button(tl!("Bring to front")).clicked() { - view.pending_edit = Some(Edit::EditPageImage { page, index: hit, change: E::Arrange(pdfcraft_engine::ArrangeOrder::BringToFront) }); - close(ui); - } - if ui.button(tl!("Send to back")).clicked() { - view.pending_edit = Some(Edit::EditPageImage { page, index: hit, change: E::Arrange(pdfcraft_engine::ArrangeOrder::SendToBack) }); - close(ui); - } - if ui.button(tl!("Bring forward")).clicked() { - view.pending_edit = Some(Edit::EditPageImage { page, index: hit, change: E::Arrange(pdfcraft_engine::ArrangeOrder::BringForward) }); - close(ui); - } - if ui.button(tl!("Send backward")).clicked() { - view.pending_edit = Some(Edit::EditPageImage { page, index: hit, change: E::Arrange(pdfcraft_engine::ArrangeOrder::SendBackward) }); - close(ui); + use pdfcraft_engine::ArrangeOrder as A; + for (order, label) in [ + (A::BringToFront, tl!("Bring to front")), + (A::SendToBack, tl!("Send to back")), + (A::BringForward, tl!("Bring forward")), + (A::SendBackward, tl!("Send backward")), + ] { + if item(ui, true, label) { + view.pending_edit = edit(E::Arrange(order)); + } } }); - if ui.add_enabled(raster, egui::Button::new(tl!("Replace image"))).clicked() { + if item(ui, raster, tl!("Replace image")) { *action = Some(ImageAction::Replace(page, hit)); - close(ui); } - if ui.add_enabled(raster, egui::Button::new(tl!("Save Image As…"))).clicked() { + if item(ui, raster, tl!("Save Image As…")) { *action = Some(ImageAction::Save(page, hit)); - close(ui); } ui.separator(); - if ui.button(tl!("Draw freehand")).clicked() { + if item(ui, true, tl!("Draw freehand")) { *action = Some(ImageAction::DrawFreehand); - close(ui); } - if ui.button(tl!("Redact Image")).clicked() { + if item(ui, true, tl!("Redact Image")) { *action = Some(ImageAction::Redact(page, hit)); - close(ui); - } - if ui.add_enabled(raster, egui::Button::new(tl!("Export image"))).clicked() { - *action = Some(ImageAction::Save(page, hit)); - close(ui); } ui.separator(); - if ui.button(tl!("Create Link…")).clicked() { + if item(ui, true, tl!("Create Link…")) { *action = Some(ImageAction::Link(page, hit)); - close(ui); } - - ui.menu_button(tl!("Edit image using"), |ui| { - ui.add_enabled(false, egui::Button::new(tl!("Default image editor"))); - }); - - if ui.button(tl!("Recognize Text…")).clicked() { + if item(ui, true, tl!("Recognize Text…")) { *action = Some(ImageAction::Recognize(page)); - close(ui); } ui.separator(); - if ui.button(tl!("Delete")).clicked() { + if item(ui, true, tl!("Delete")) { view.image_selection = None; - view.pending_edit = Some(Edit::EditPageImage { page, index: hit, change: E::Delete }); - close(ui); + view.pending_edit = edit(E::Delete); } } diff --git a/crates/ui-egui/tests/it/editing.rs b/crates/ui-egui/tests/it/editing.rs index 54fae86c83..b85f7650e2 100644 --- a/crates/ui-egui/tests/it/editing.rs +++ b/crates/ui-egui/tests/it/editing.rs @@ -4616,9 +4616,7 @@ fn image_right_click_context_menu_items_and_actions() { "Replace image", "Draw freehand", "Redact Image", - "Export image", "Create Link", - "Edit image using", "Recognize Text", ] { h.get_by_label_contains(item);