From 50ae733d66877376db09bbf2b6b33fb1d376971c Mon Sep 17 00:00:00 2001 From: aiongg Date: Thu, 22 Oct 2026 04:09:12 +0000 Subject: [PATCH 1/4] =?UTF-8?q?Integration:=20Alt+arrow=20leaves=20a=20sel?= =?UTF-8?q?ected=20field=20in=20place=20for=20Calculation=20order=20(#2621?= =?UTF-8?q?=20=C3=97=20#3733)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prepare a form's arrow-key nudge (#2621) consumed Alt+↑/↓ as plain arrows, because egui's consume_key ignores an extra Alt. Calculation order (#3733) moves the selected field with Alt+↑/↓, so the field moved a point on the page instead of in the order. The nudge now leaves Alt+arrow alone. --- crates/ui-egui/src/prepare.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/ui-egui/src/prepare.rs b/crates/ui-egui/src/prepare.rs index 22cd4e640..13767e55f 100644 --- a/crates/ui-egui/src/prepare.rs +++ b/crates/ui-egui/src/prepare.rs @@ -569,7 +569,8 @@ pub(crate) fn delete_selected(view: &mut DocView) -> Option { /// as the object nudge does (`object_ui::nudge_keys`) and as dragging it does (#2608). The keys /// are consumed before the view turns the page with them; one press is one undo step. pub(crate) fn nudge_keys(ctx: &egui::Context, view: &mut DocView, doc: &Document) { - if ctx.egui_wants_keyboard_input() || view.prepare.grab.is_some() { + // `consume_key` lets an extra Alt through, and Alt+↑/↓ belongs to Calculation order's move. + if ctx.egui_wants_keyboard_input() || view.prepare.grab.is_some() || ctx.input(|i| i.modifiers.alt) { return; } let Some((name, wi)) = view.prepare.selected.clone() else { return }; From 8341a721e07f74e92b6663ded763bc3fbcad7f68 Mon Sep 17 00:00:00 2001 From: aiongg Date: Thu, 22 Oct 2026 04:19:39 +0000 Subject: [PATCH 2/4] =?UTF-8?q?Integration:=20mode-tab=20hand=20tests=20us?= =?UTF-8?q?e=20the=20tabs=20the=20mode=20bar=20has=20(#2240=20=C3=97=20#30?= =?UTF-8?q?10,=20#3022)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #2240 (on main before this round) took the Read tab out of the mode bar. #3010 and #3022 were written against a mode bar that still had it, so their five tests looked for a Read button that is gone. The tests now check All tools in its place where the scrolled content reaches under it (added text, the Fill & Sign type box). Page 1's text and the form's name field start right of All tools, so those tests check Edit, Convert and E-Sign, the tabs they lie under. --- crates/ui-egui/tests/content.rs | 6 +++--- crates/ui-egui/tests/editing.rs | 15 ++++++--------- crates/ui-egui/tests/fill_sign.rs | 6 +++--- crates/ui-egui/tests/forms.rs | 15 ++++++--------- 4 files changed, 18 insertions(+), 24 deletions(-) diff --git a/crates/ui-egui/tests/content.rs b/crates/ui-egui/tests/content.rs index 2c2ad4c6b..d75de6118 100644 --- a/crates/ui-egui/tests/content.rs +++ b/crates/ui-egui/tests/content.rs @@ -713,7 +713,7 @@ fn mode_tab(h: &Harness<'static, PdfCraftApp>, label: &str) -> egui::Rect { } /// Scroll the page until `what` (on screen) lies across the mode bar, then the cursor over each -/// tab it lies under: with the All tools panel closed, Read, Edit, Convert and E-Sign. +/// tab it lies under: with the All tools panel closed, every tab: All tools, Edit, Convert and E-Sign. fn mode_tab_cursors( h: &mut Harness<'static, PdfCraftApp>, what: impl Fn(&Harness<'static, PdfCraftApp>) -> egui::Rect, @@ -727,7 +727,7 @@ fn mode_tab_cursors( h.state_mut().views[0].goto = Some((0, (top - (tabs - y)) / page(h).height())); h.run_steps(4); let under = what(h); - ["Read", "Edit", "Convert", "E-Sign"].map(|label| { + ["All tools", "Edit", "Convert", "E-Sign"].map(|label| { let at = mode_tab(h, label).center(); assert!(under.contains(at), "{under:?} lies under {label} at {at:?}"); h.hover_at(at); @@ -737,7 +737,7 @@ fn mode_tab_cursors( } const HAND_ON_EVERY_TAB: [(&str, egui::CursorIcon); 4] = [ - ("Read", egui::CursorIcon::PointingHand), + ("All tools", egui::CursorIcon::PointingHand), ("Edit", egui::CursorIcon::PointingHand), ("Convert", egui::CursorIcon::PointingHand), ("E-Sign", egui::CursorIcon::PointingHand), diff --git a/crates/ui-egui/tests/editing.rs b/crates/ui-egui/tests/editing.rs index ebb910a3d..b0136a2bd 100644 --- a/crates/ui-egui/tests/editing.rs +++ b/crates/ui-egui/tests/editing.rs @@ -5879,12 +5879,13 @@ fn mode_tab(h: &Harness<'static, PdfCraftApp>, label: &str) -> egui::Rect { } /// The cursor over each mode bar tab that page 1's text lies under, once that text is scrolled -/// up under the bar. With the All tools panel closed it lies under Read, Edit, Convert and E-Sign. -fn mode_tab_cursors(h: &mut Harness<'static, PdfCraftApp>, text: egui::Rect) -> [(&'static str, egui::CursorIcon); 4] { +/// up under the bar. With the All tools panel closed it lies under Edit, Convert and E-Sign; it +/// starts right of All tools. +fn mode_tab_cursors(h: &mut Harness<'static, PdfCraftApp>, text: egui::Rect) -> [(&'static str, egui::CursorIcon); 3] { h.state_mut().views[0].goto = Some((0, 0.5)); h.run_steps(4); let offset = h.state().views[0].page_screen_rect(0).expect("on screen").top(); - ["Read", "Edit", "Convert", "E-Sign"].map(|label| { + ["Edit", "Convert", "E-Sign"].map(|label| { let at = mode_tab(h, label).center(); assert!(text.translate(egui::vec2(0.0, offset)).contains(at), "the text lies under {label}"); h.hover_at(at); @@ -5893,12 +5894,8 @@ fn mode_tab_cursors(h: &mut Harness<'static, PdfCraftApp>, text: egui::Rect) -> }) } -const HAND_ON_EVERY_TAB: [(&str, egui::CursorIcon); 4] = [ - ("Read", egui::CursorIcon::PointingHand), - ("Edit", egui::CursorIcon::PointingHand), - ("Convert", egui::CursorIcon::PointingHand), - ("E-Sign", egui::CursorIcon::PointingHand), -]; +const HAND_ON_EVERY_TAB: [(&str, egui::CursorIcon); 3] = + [("Edit", egui::CursorIcon::PointingHand), ("Convert", egui::CursorIcon::PointingHand), ("E-Sign", egui::CursorIcon::PointingHand)]; /// Page 1's text block on screen, relative to the page's top edge, in the window as it is now. fn page_text(h: &Harness<'static, PdfCraftApp>) -> egui::Rect { diff --git a/crates/ui-egui/tests/fill_sign.rs b/crates/ui-egui/tests/fill_sign.rs index 7a54fc2ca..77bab6e91 100644 --- a/crates/ui-egui/tests/fill_sign.rs +++ b/crates/ui-egui/tests/fill_sign.rs @@ -1667,7 +1667,7 @@ fn mode_tab(h: &Harness<'static, PdfCraftApp>, label: &str) -> egui::Rect { } /// Scroll the page until `what` (on screen) lies across the mode bar, then the cursor over each -/// tab it lies under: with the All tools panel closed, Read, Edit, Convert and E-Sign. +/// tab it lies under: with the All tools panel closed, every tab: All tools, Edit, Convert and E-Sign. fn mode_tab_cursors( h: &mut Harness<'static, PdfCraftApp>, what: impl Fn(&Harness<'static, PdfCraftApp>) -> egui::Rect, @@ -1681,7 +1681,7 @@ fn mode_tab_cursors( h.state_mut().views[0].goto = Some((0, (top - (tabs - y)) / page(h).height())); h.run_steps(4); let under = what(h); - ["Read", "Edit", "Convert", "E-Sign"].map(|label| { + ["All tools", "Edit", "Convert", "E-Sign"].map(|label| { let at = mode_tab(h, label).center(); assert!(under.contains(at), "{under:?} lies under {label} at {at:?}"); h.hover_at(at); @@ -1691,7 +1691,7 @@ fn mode_tab_cursors( } const HAND_ON_EVERY_TAB: [(&str, egui::CursorIcon); 4] = [ - ("Read", egui::CursorIcon::PointingHand), + ("All tools", egui::CursorIcon::PointingHand), ("Edit", egui::CursorIcon::PointingHand), ("Convert", egui::CursorIcon::PointingHand), ("E-Sign", egui::CursorIcon::PointingHand), diff --git a/crates/ui-egui/tests/forms.rs b/crates/ui-egui/tests/forms.rs index 1528d4c5e..efc27dc83 100644 --- a/crates/ui-egui/tests/forms.rs +++ b/crates/ui-egui/tests/forms.rs @@ -1282,11 +1282,12 @@ fn mode_tab(h: &Harness<'static, PdfCraftApp>, label: &str) -> egui::Rect { } /// Scroll the page until `what` (on screen) lies across the mode bar, then the cursor over each -/// tab it lies under: with the All tools panel closed, Read, Edit, Convert and E-Sign. +/// tab it lies under: with the All tools panel closed, Edit, Convert and E-Sign (the name field +/// starts right of All tools). fn mode_tab_cursors( h: &mut Harness<'static, PdfCraftApp>, what: impl Fn(&Harness<'static, PdfCraftApp>) -> egui::Rect, -) -> [(&'static str, egui::CursorIcon); 4] { +) -> [(&'static str, egui::CursorIcon); 3] { let page = |h: &Harness<'static, PdfCraftApp>| h.state().views[0].page_screen_rect(0).expect("on screen"); let y = what(h).center().y - page(h).top(); let tabs = mode_tab(h, "Convert").center().y; @@ -1296,7 +1297,7 @@ fn mode_tab_cursors( h.state_mut().views[0].goto = Some((0, (top - (tabs - y)) / page(h).height())); h.run_steps(4); let under = what(h); - ["Read", "Edit", "Convert", "E-Sign"].map(|label| { + ["Edit", "Convert", "E-Sign"].map(|label| { let at = mode_tab(h, label).center(); assert!(under.contains(at), "{under:?} lies under {label} at {at:?}"); h.hover_at(at); @@ -1305,12 +1306,8 @@ fn mode_tab_cursors( }) } -const HAND_ON_EVERY_TAB: [(&str, egui::CursorIcon); 4] = [ - ("Read", egui::CursorIcon::PointingHand), - ("Edit", egui::CursorIcon::PointingHand), - ("Convert", egui::CursorIcon::PointingHand), - ("E-Sign", egui::CursorIcon::PointingHand), -]; +const HAND_ON_EVERY_TAB: [(&str, egui::CursorIcon); 3] = + [("Edit", egui::CursorIcon::PointingHand), ("Convert", egui::CursorIcon::PointingHand), ("E-Sign", egui::CursorIcon::PointingHand)]; /// A text field scrolled up under the mode bar leaves its tabs the hand (#2755). The field gave /// them its I-beam, and its open editor floated over them and took their clicks. From 46417784e1271be1c4485f035fb74b25e0848574 Mon Sep 17 00:00:00 2001 From: aiongg Date: Thu, 22 Oct 2026 04:22:31 +0000 Subject: [PATCH 3/4] =?UTF-8?q?Integration:=20the=20live=20line-resize=20t?= =?UTF-8?q?est=20follows=20lines=20across=20rotated=20pages=20(#3520=20?= =?UTF-8?q?=C3=97=20#3781)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #3781 draws a Fill & Sign line across the page as /Rotate displays it, so on a page turned a quarter its rect runs along user y. #3520's test expected the line along user x on every page and failed on the /Rotate 90 cases. The test's points and rects are now given along the line, so each rotation checks the same grow, shrink and release. --- crates/ui-egui/tests/fill_sign.rs | 33 +++++++++++++++++++------------ 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/crates/ui-egui/tests/fill_sign.rs b/crates/ui-egui/tests/fill_sign.rs index 77bab6e91..61fb5fe6d 100644 --- a/crates/ui-egui/tests/fill_sign.rs +++ b/crates/ui-egui/tests/fill_sign.rs @@ -1331,6 +1331,13 @@ fn lines_grow_and_shrink_with_their_handles_before_release() { let _gpu = gpu(); for (document_rotation, view_rotation) in [(0, 0), (90, 0), (0, 90), (90, 270)] { let rot = (document_rotation, view_rotation); + // A line runs across the page as /Rotate displays it (#3781): along user y on a quarter turn. + // `d` is the distance along the line from the click at (100, 250). + let along = + |h: &Harness<'static, PdfCraftApp>, d: f32| if document_rotation == 90 { at(h, 100.0, 250.0 + d) } else { at(h, 100.0 + d, 250.0) }; + let line = |from: f32, to: f32| { + if document_rotation == 90 { [98.0, 250.0 + from, 102.0, 250.0 + to] } else { [100.0 + from, 248.0, 100.0 + to, 252.0] } + }; let mut h = harness(); if document_rotation != 0 { h.state_mut().apply_edit(pdfcraft_engine::Edit::RotatePages { pages: vec![0], degrees: document_rotation }); @@ -1341,36 +1348,36 @@ fn lines_grow_and_shrink_with_their_handles_before_release() { h.run_steps(3); assert!(h.state_mut().execute("sign.fill.line")); click(&mut h, 100.0, 250.0); - assert_rect(rects(&h)[0], [82.0, 248.0, 118.0, 252.0]); + assert_rect(rects(&h)[0], line(-18.0, 18.0)); // In line mode a click on the line picks it up, with its handles. click(&mut h, 100.0, 250.0); assert_eq!(h.state().views[0].comments.selected, Some((0, 0)), "{rot:?}"); let generation = h.state().session.get(h.state().views[0].id).unwrap().edit_generation(); - // The handle at the line's end, dragged out to 160. - h.drag_at(at(&h, 118.0, 250.0)); + // The handle at the line's end, dragged out to 60 from the middle. + h.drag_at(along(&h, 18.0)); h.run_steps(1); - h.hover_at(at(&h, 160.0, 250.0)); + h.hover_at(along(&h, 60.0)); // Wait only for the one-time layers. Later pointer frames are immediate. let mut grown = h.render().unwrap(); for _ in 0..50 { - if ink_near(&grown, at(&h, 150.0, 250.0)) { + if ink_near(&grown, along(&h, 50.0)) { break; } h.run_steps(2); std::thread::sleep(std::time::Duration::from_millis(10)); grown = h.render().unwrap(); } - assert!(ink_near(&grown, at(&h, 150.0, 250.0)), "{rot:?}: the line grows with the handle before release"); - assert!(ink_near(&grown, at(&h, 90.0, 250.0)), "{rot:?}: from where it starts"); + assert!(ink_near(&grown, along(&h, 50.0)), "{rot:?}: the line grows with the handle before release"); + assert!(ink_near(&grown, along(&h, -10.0)), "{rot:?}: from where it starts"); // Back in, shorter than it was, still without letting go. - h.hover_at(at(&h, 100.0, 250.0)); + h.hover_at(along(&h, 0.0)); h.run_steps(2); let shrunk = h.render().unwrap(); - assert!(ink_near(&shrunk, at(&h, 90.0, 250.0)), "{rot:?}: the line still starts where it did"); - for x in [110.0, 150.0] { - assert!(!ink_near(&shrunk, at(&h, x, 250.0)), "{rot:?}: the line shrinks with the handle before release (ink at {x})"); + assert!(ink_near(&shrunk, along(&h, -10.0)), "{rot:?}: the line still starts where it did"); + for d in [10.0, 50.0] { + assert!(!ink_near(&shrunk, along(&h, d)), "{rot:?}: the line shrinks with the handle before release (ink at {d})"); } let doc = h.state().session.get(h.state().views[0].id).unwrap(); assert_eq!((doc.edit_generation(), doc.can_undo()), (generation, Some("Add line")), "{rot:?}: pointer frames never edit the document"); @@ -1378,12 +1385,12 @@ fn lines_grow_and_shrink_with_their_handles_before_release() { grown.save(format!("{dir}/line-live-grow-{document_rotation}-{view_rotation}.png")).unwrap(); shrunk.save(format!("{dir}/line-live-shrink-{document_rotation}-{view_rotation}.png")).unwrap(); } - h.drop_at(at(&h, 100.0, 250.0)); + h.drop_at(along(&h, 0.0)); h.run_steps(3); let doc = h.state().session.get(h.state().views[0].id).unwrap(); assert_eq!(doc.can_undo(), Some("Resize comment"), "{rot:?}"); let [now] = rects(&h)[..] else { panic!("{rot:?}: still one line: {:?}", rects(&h)) }; - assert!(now.iter().zip([82.0, 248.0, 100.0, 252.0]).all(|(a, b)| (a - b).abs() < 0.01), "{rot:?}: {now:?}"); + assert!(now.iter().zip(line(-18.0, 0.0)).all(|(a, b)| (a - b).abs() < 0.01), "{rot:?}: {now:?}"); } } From 9a2d2cfdca4c24d420bd1166548191d970d61812 Mon Sep 17 00:00:00 2001 From: aiongg Date: Thu, 22 Oct 2026 04:22:53 +0000 Subject: [PATCH 4/4] =?UTF-8?q?Integration:=20panel=20snapshots=20for=20th?= =?UTF-8?q?e=20assembled=20batch=20(#3525=20=C3=97=20#2607,=20#2731,=20#38?= =?UTF-8?q?28)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #3525 asked for its snapshots to be regenerated once the batch is assembled. Each change comes from a merged PR: - panel_compare: the Show: Text / Visual filters (#2607). - panel_layers: the Layer options button (#2731, Reset to Initial Visibility). - panel_pages: the thumbnail size label, buttons and slider (#3828). --- crates/ui-egui/tests/snapshots/panel_compare.txt | 3 +++ crates/ui-egui/tests/snapshots/panel_layers.txt | 1 + crates/ui-egui/tests/snapshots/panel_pages.txt | 5 +++++ 3 files changed, 9 insertions(+) diff --git a/crates/ui-egui/tests/snapshots/panel_compare.txt b/crates/ui-egui/tests/snapshots/panel_compare.txt index a2d8cc5a6..2559b6d45 100644 --- a/crates/ui-egui/tests/snapshots/panel_compare.txt +++ b/crates/ui-egui/tests/snapshots/panel_compare.txt @@ -5,6 +5,9 @@ Label = "2 Replaced" Label = "0 Inserted" Label = "0 Deleted" Label = "1 Visual" +Label = "Show:" +CheckBox "Text" [on] +CheckBox "Visual" [on] Button "Side by side" Button "New file only" Button "Mark as comments" diff --git a/crates/ui-egui/tests/snapshots/panel_layers.txt b/crates/ui-egui/tests/snapshots/panel_layers.txt index 1e75f6bf6..004cf313b 100644 --- a/crates/ui-egui/tests/snapshots/panel_layers.txt +++ b/crates/ui-egui/tests/snapshots/panel_layers.txt @@ -1,4 +1,5 @@ Label = "Layers" Label = "1" Button "Close" [off] +Button "Layer options" [off] CheckBox "Draft notes" [on] diff --git a/crates/ui-egui/tests/snapshots/panel_pages.txt b/crates/ui-egui/tests/snapshots/panel_pages.txt index 726d58208..4e746f100 100644 --- a/crates/ui-egui/tests/snapshots/panel_pages.txt +++ b/crates/ui-egui/tests/snapshots/panel_pages.txt @@ -1,6 +1,11 @@ Label = "Pages" Label = "2" Button "Close" [off] +Label = "Thumbnail size" +Label = "100%" +Button "Smaller thumbnails" [off] +Slider "Thumbnail size" +Button "Larger thumbnails" [off] Button "Page 1" [off] Label = "1" Button "Page 2" [off]