From 54b91598a3ea9773562902c4837d478940ec1f06 Mon Sep 17 00:00:00 2001 From: FredApps <153173854+FredApps@users.noreply.github.com> Date: Sat, 14 Nov 2026 22:25:50 +0100 Subject: [PATCH] Move selected comments together with arrow keys (#7165) * Move selected comments together with arrow keys * Test keyboard nudging of selected lines * test(ui): cover locked and duplicate nudge selections --- crates/ui-egui/src/tools/comments/input.rs | 22 ++++++- crates/ui-egui/tests/it/comments.rs | 77 ++++++++++++++++++++++ 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/tools/comments/input.rs b/crates/ui-egui/src/tools/comments/input.rs index 79b69a6eba..a23b8e26ae 100644 --- a/crates/ui-egui/src/tools/comments/input.rs +++ b/crates/ui-egui/src/tools/comments/input.rs @@ -788,7 +788,7 @@ pub(crate) fn delete_selected(cv: &mut CommentView, info: &DocInfo) -> Option = comments(&h).iter().map(|a| a.rect).collect(); + assert_eq!(before.len(), 2); + h.state_mut().views[0].comments.selected = Some((0, 0)); + h.state_mut().views[0].comments.also = vec![(0, 1)]; + h.key_press(Key::ArrowRight); + h.run_steps(3); + let moved: Vec<_> = comments(&h).iter().map(|a| a.rect).collect(); + for (old, new) in before.iter().zip(&moved) { + assert!(new[0] > old[0], "each selected comment moves: {before:?} -> {moved:?}"); + } + h.state_mut().undo(); + h.run_steps(2); + let undone: Vec<_> = comments(&h).iter().map(|a| a.rect).collect(); + assert_eq!(undone, before, "one undo restores the whole selection"); +} + +#[test] +fn arrow_nudge_skips_locked_comments_and_duplicate_selection_entries() { + let mut h = harness(|app| app.set_option("quick", "square").unwrap()); + for (a, b) in [((20.0, 20.0), (60.0, 60.0)), ((100.0, 20.0), (140.0, 60.0)), ((180.0, 20.0), (220.0, 60.0))] { + drag_pt(&mut h, a, b); + } + h.state_mut().set_option("quick", "select").unwrap(); + assert!(h.state_mut().apply_edit(pdfcraft_engine::Edit::LockAnnotation { page: 0, index: 1, locked: true })); + h.run_steps(2); + let before = comments(&h); + h.state_mut().views[0].comments.selected = Some((0, 0)); + h.state_mut().views[0].comments.also = vec![(0, 1), (0, 2), (0, 2), (0, 0)]; + h.key_press(egui::Key::ArrowRight); + h.run_steps(3); + let after = comments(&h); + let left = |list: &[Annotation], i: usize| list.iter().find(|a| a.index == i).expect("comment still exists").rect[0]; + let primary_delta = left(&after, 0) - left(&before, 0); + let secondary_delta = left(&after, 2) - left(&before, 2); + assert!(primary_delta > 0.0, "primary comment moves"); + assert!((secondary_delta - primary_delta).abs() < 0.01, "duplicate entries must not move the second comment twice"); + assert_eq!(left(&after, 1), left(&before, 1), "locked comment stays in place"); + h.state_mut().undo(); + h.run_steps(2); + let undone = comments(&h); + for i in 0..3 { + assert_eq!(left(&undone, i), left(&before, i), "one undo restores comment {i}"); + } +} + /// #3632: with a comment selected, Alt+← / Alt+→ still go back and forward through the view /// history instead of nudging it (egui's key patterns let an extra Alt through). #[test] @@ -5848,3 +5900,28 @@ fn properties_dialog_makes_the_text_box_font_size_default() { assert_eq!(font_size_of_last(&fresh).font_size, Some(size), "{quick}: the default font size persisted"); } } + +#[test] +fn ctrl_selected_lines_nudge_together_and_undo() { + let mut h = harness(|app| app.set_option("quick", "line").unwrap()); + for y in [40.0, 80.0, 120.0] { + drag_pt(&mut h, (40.0, y), (160.0, y)); + } + assert_eq!(comments(&h).len(), 3); + h.state_mut().set_option("quick", "select").unwrap(); + h.run_steps(2); + click_pt(&mut h, (100.0, 40.0)); + click_pt_with(&mut h, (100.0, 80.0), egui::Modifiers::COMMAND); + click_pt_with(&mut h, (100.0, 120.0), egui::Modifiers::COMMAND); + assert_eq!(h.state().views[0].comments.selection().len(), 3); + let before: Vec<_> = comments(&h).iter().map(|a| a.rect).collect(); + h.key_press(egui::Key::ArrowRight); + h.run_steps(3); + let moved: Vec<_> = comments(&h).iter().map(|a| a.rect).collect(); + for (old, new) in before.iter().zip(&moved) { + assert!(new[0] > old[0], "each selected line moves: {before:?} -> {moved:?}"); + } + h.state_mut().undo(); + h.run_steps(2); + assert_eq!(comments(&h).iter().map(|a| a.rect).collect::>(), before); +}