diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index d67b18b802..f2b5720190 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -1145,7 +1145,8 @@ fn page_tool_input(ui: &mut egui::Ui, view: &mut DocView, frame: &mut PageFrame< && let Some(p) = ptr.within(xf.rect) { view.cursor.claim(Layer::ToolArea, egui::CursorIcon::Crosshair); - if resp.clicked() { + // The tool's own click: nothing drawn over the page here holds the press. + if resp.clicked() && view.router.owner(i).is_none() { // Centred on the click, upright as the page is shown. let (vx, vy) = xf.screen_to_view(p); let (w, h) = kind.size(); @@ -1169,7 +1170,8 @@ fn page_tool_input(ui: &mut egui::Ui, view: &mut DocView, frame: &mut PageFrame< && let Some(p) = ptr.within(xf.rect) { view.cursor.claim(Layer::ToolArea, egui::CursorIcon::Crosshair); - if resp.clicked() { + // The tool's own click: nothing drawn over the page here holds the press. + if resp.clicked() && view.router.owner(i).is_none() { // Centred on the click at its natural size (the engine sizes it). let (vx, vy) = xf.screen_to_view(p); let u = info.pages[i].view_to_user(vx, vy); diff --git a/crates/ui-egui/src/canvas/crop.rs b/crates/ui-egui/src/canvas/crop.rs index dd88db1a19..bdd1e9e135 100644 --- a/crates/ui-egui/src/canvas/crop.rs +++ b/crates/ui-egui/src/canvas/crop.rs @@ -38,15 +38,18 @@ pub(crate) fn page_input(ui: &egui::Ui, input: CropPageInput<'_>) -> bool { if !allowed { return false; } - if resp.drag_started() + // The tool's own press, of the primary button: nothing drawn over the page here holds it. + if resp.drag_started_by(egui::PointerButton::Primary) + && view.router.owner(page).is_none() && let Some(o) = origin.filter(|o| xf.rect.contains(*o)) { view.crop_drag = Some((page, o)); } if let Some((p, start)) = view.crop_drag && p == page - && let Some(end) = pointer { + // Where the pointer is, or where it went down when it is off the window. + let end = pointer.unwrap_or(start); let end = Pos2::new(end.x.clamp(xf.rect.left(), xf.rect.right()), end.y.clamp(xf.rect.top(), xf.rect.bottom())); let r = Rect::from_two_pos(start, end); // Dim what will be cut away. @@ -60,7 +63,7 @@ pub(crate) fn page_input(ui: &egui::Ui, input: CropPageInput<'_>) -> bool { painter.rect_filled(band, CornerRadius::ZERO, Color32::from_black_alpha(70)); } painter.rect_stroke(r, CornerRadius::ZERO, Stroke::new(1.0, Color32::from_rgb(0x14, 0x73, 0xE6)), egui::StrokeKind::Middle); - if resp.drag_stopped() { + if resp.drag_stopped() || !ptr.primary_down { view.crop_drag = None; if r.width() >= 4.0 && r.height() >= 4.0 { let (a, b) = (to_user(xf, info, page, r.left_top()), to_user(xf, info, page, r.right_bottom())); diff --git a/crates/ui-egui/src/canvas/zoom_snap.rs b/crates/ui-egui/src/canvas/zoom_snap.rs index 8e530ac58e..5a0dcf6733 100644 --- a/crates/ui-egui/src/canvas/zoom_snap.rs +++ b/crates/ui-egui/src/canvas/zoom_snap.rs @@ -19,19 +19,22 @@ pub(crate) fn page_input(ui: &egui::Ui, resp: &egui::Response, xf: &PageXform, p if pointer.is_some_and(|p| xf.rect.contains(p)) { view.cursor.claim(Layer::ToolArea, egui::CursorIcon::Crosshair); } - if resp.drag_started() + // The tool's own press, of the primary button: nothing drawn over the page here holds it. + if resp.drag_started_by(egui::PointerButton::Primary) + && view.router.owner(page).is_none() && let Some(o) = origin.filter(|o| xf.rect.contains(*o)) { view.marquee = Some((page, o)); } if let Some((p, start)) = view.marquee && p == page - && let Some(end) = pointer { + // Where the pointer is, or where it went down when it is off the window. + let end = pointer.unwrap_or(start); let end = Pos2::new(end.x.clamp(xf.rect.left(), xf.rect.right()), end.y.clamp(xf.rect.top(), xf.rect.bottom())); let r = Rect::from_two_pos(start, end); ui.painter().rect_stroke(r, CornerRadius::ZERO, Stroke::new(1.0, Color32::from_rgb(0x14, 0x73, 0xE6)), egui::StrokeKind::Middle); - if resp.drag_stopped() { + if resp.drag_stopped() || !ptr.primary_down { view.marquee = None; let (a, b) = (xf.screen_to_view(r.left_top()), xf.screen_to_view(r.right_bottom())); view.marquee_done = Some((page, r, [a.0.min(b.0), a.1.min(b.1), a.0.max(b.0), a.1.max(b.1)])); diff --git a/crates/ui-egui/src/tools/redact_ui.rs b/crates/ui-egui/src/tools/redact_ui.rs index e5c2ac06e5..96b544b4d0 100644 --- a/crates/ui-egui/src/tools/redact_ui.rs +++ b/crates/ui-egui/src/tools/redact_ui.rs @@ -256,7 +256,8 @@ pub(crate) fn page_input(cx: PageToolContext<'_>, over_text: impl Fn(Pos2) -> bo if !over_text(p) { view.cursor.claim(Layer::ToolArea, egui::CursorIcon::Crosshair); } - if resp.drag_started() { + // The tool's own press, of the primary button: nothing drawn over the page here holds it. + if resp.drag_started_by(egui::PointerButton::Primary) && view.router.owner(page).is_none() { let origin = ptr.origin.unwrap_or(p); if !over_text(origin) { view.redact_drag = Some((page, origin)); diff --git a/crates/ui-egui/src/tools/sign_ui.rs b/crates/ui-egui/src/tools/sign_ui.rs index 921f9a738d..b00d07279a 100644 --- a/crates/ui-egui/src/tools/sign_ui.rs +++ b/crates/ui-egui/src/tools/sign_ui.rs @@ -176,20 +176,23 @@ pub(crate) fn page_input( if pointer.is_some_and(|p| xf.rect.contains(p)) { view.cursor.claim(Layer::ToolArea, egui::CursorIcon::Crosshair); } - if resp.drag_started() + // The tool's own press, of the primary button: nothing drawn over the page here holds it. + if resp.drag_started_by(egui::PointerButton::Primary) + && view.router.owner(page).is_none() && let Some(o) = origin.filter(|o| xf.rect.contains(*o)) { view.sign.drag = Some((page, o)); } if let Some((p, start)) = view.sign.drag && p == page - && let Some(end) = pointer { + // Where the pointer is, or where it went down when it is off the window. + let end = pointer.unwrap_or(start); let end = Pos2::new(end.x.clamp(xf.rect.left(), xf.rect.right()), end.y.clamp(xf.rect.top(), xf.rect.bottom())); let r = Rect::from_two_pos(start, end); ui.painter().rect_filled(r, CornerRadius::ZERO, Color32::from_rgba_unmultiplied(0x14, 0x73, 0xE6, 28)); ui.painter().rect_stroke(r, CornerRadius::ZERO, Stroke::new(1.0, Color32::from_rgb(0x14, 0x73, 0xE6)), egui::StrokeKind::Middle); - if resp.drag_stopped() { + if resp.drag_stopped() || !ptr.primary_down { view.sign.drag = None; if r.width() >= 8.0 && r.height() >= 8.0 { let user = |q: Pos2| { diff --git a/crates/ui-egui/tests/it/pointer.rs b/crates/ui-egui/tests/it/pointer.rs index fbd3d720e4..4a28943b11 100644 --- a/crates/ui-egui/tests/it/pointer.rs +++ b/crates/ui-egui/tests/it/pointer.rs @@ -744,6 +744,27 @@ fn escape_cancels_a_signature_rectangle() { assert!(s.dialog.is_none(), "and no signing dialog opens: {:?}", s.dialog); } +/// Only the primary button draws a signature rectangle: a right-drag opens no signing dialog. +#[test] +fn a_right_drag_draws_no_signature_rectangle() { + let mut h = open(link_pdf(), "150", false); + h.state_mut().quick_tool = QuickTool::SignArea { certify: false }; + h.run_steps(4); + let (a, b) = (at(&h, 60.0, 200.0), at(&h, 200.0, 120.0)); + let button = |pos, pressed| egui::Event::PointerButton { pos, button: egui::PointerButton::Secondary, pressed, modifiers: egui::Modifiers::NONE }; + h.hover_at(a); + h.run_steps(1); + h.event(button(a, true)); + h.run_steps(1); + h.hover_at(b); + h.run_steps(2); + h.event(button(b, false)); + h.run_steps(4); + let s = h.state(); + assert_eq!(s.quick_tool, QuickTool::SignArea { certify: false }, "the tool stays: no rectangle was drawn"); + assert!(s.dialog.is_none(), "and no signing dialog opens: {:?}", s.dialog); +} + /// Escape with the button down drops the press: its release places no stamp, and the next click /// places one. #[test]