mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-31 07:39:08 +00:00
fix(ui): the area and placement tools act only on their own presses, of the primary button
The Sign area, marquee zoom and snapshot, crop and redaction boxes, and the stamps, act where nothing drawn over the page holds the press (the page router has no owner for it), rather than wherever the press lands on the page. Fill & Sign already asks the router (fill_grabs). Their drags start on the primary button and end with its release, however egui reported it: its drag stop, or the button found up. They started on any button, so a right-drag drew a signature rectangle and opened the signing dialog (and drew a crop or a marquee), and the end waited for a pointer position, so a drag whose release came without one never ended. New test a_right_drag_draws_no_signature_rectangle, seen failing with the drag started on any button. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
6 files changed
+45
-12
No files matched your search
@@ -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);
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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)]));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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| {
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in new issue
Block a user