From 11bc8c56528fc3a820669d41c0eec29310cdad24 Mon Sep 17 00:00:00 2001 From: alehel Date: Thu, 29 Oct 2026 15:32:48 +0100 Subject: [PATCH] fix(ui): Add text finishes a paragraph drag that leaves the paragraph's box With Add text the page's own paragraphs took input only while the pointer was over one (over_source), and their drags end in edit_text_ui::page_input. A paragraph dragged off its box was left hanging after the release (block_drag), to land wherever the pointer next met a paragraph; one flicked off in its first move never started, so nothing moved. main has the same test. Add text now also acts on the paragraph the page router says holds the press, until the button is up, so the drag ends where it is released, as with Edit text, and Add text's new-box path does not take the press over. New test add_text_finishes_a_paragraph_drag_that_leaves_its_box, seen failing before the fix (the drag still pending after the release); the flick was checked by hand the same way. From CodeRabbit's third review of #5300. Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/canvas.rs | 8 ++++++-- crates/ui-egui/tests/it/editing.rs | 33 ++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 03bfa167a1..c39fdedef1 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -1275,9 +1275,13 @@ fn page_tool_input(ui: &mut egui::Ui, view: &mut DocView, frame: &mut PageFrame< crate::redact_ui::page_input(cx, over_text, view) }; let on_objects = tool == QuickTool::EditText && can_modify && crate::object_ui::page_input(cx, view); + // Add text acts on the page's own paragraphs over them, and on the one its press was on until + // the button is up: a drag that leaves the box still ends, and no new box takes it over. let over_source = tool == QuickTool::AddText - && pointer - .is_some_and(|p| !crate::content_ui::hits_added(&xf, info, i, added, p) && crate::edit_text_ui::over_paragraph(&xf, info, i, &lines, p)); + && (matches!(view.router.owner(i), Some(crate::page_router::Owner::Paragraph { .. })) + || pointer.is_some_and(|p| { + !crate::content_ui::hits_added(&xf, info, i, added, p) && crate::edit_text_ui::over_paragraph(&xf, info, i, &lines, p) + })); let source_blocked = over_source && view.content.hold_blocked(); if over_source && resp.clicked() && !source_blocked { if let Some(edit) = view.content.finish_draft(added) { diff --git a/crates/ui-egui/tests/it/editing.rs b/crates/ui-egui/tests/it/editing.rs index 508e27bab2..baeee71f5c 100644 --- a/crates/ui-egui/tests/it/editing.rs +++ b/crates/ui-egui/tests/it/editing.rs @@ -3994,6 +3994,39 @@ fn add_text_opens_a_paragraph_from_just_inside_its_edge() { assert!(h.state().views[0].content.draft.is_none(), "and starts no text box"); } +/// With Add text a paragraph dragged off its own box still moves to where the button comes up, as +/// with Edit text. Add text acted on the page's paragraphs only while the pointer was over one, so +/// the drag was left hanging, to land wherever the pointer next met a paragraph. +#[test] +fn add_text_finishes_a_paragraph_drag_that_leaves_its_box() { + let mut h = harness(2, |_| {}); + h.state_mut().quick_tool = pdfcraft_ui_egui::QuickTool::AddText; + h.run_steps(2); + let id = h.state().views[0].id; + let before = h.state().session.get(id).unwrap().text_blocks(0)[0].rect; + let r = h.state().views[0].page_screen_rect(0).expect("on screen"); + let k = r.width() / 200.0; + let from = egui::pos2(r.left() + ((before[0] + before[2]) / 2.0) as f32 * k, r.top() + (300.0 - ((before[1] + before[3]) / 2.0) as f32) * k); + // The drag starts in the box (8 px down), then goes 60 pt down, onto the blank page under it. + h.hover_at(from); + h.run_steps(1); + h.drag_at(from); + h.run_steps(1); + h.hover_at(from + egui::vec2(0.0, 8.0)); + h.run_steps(1); + for i in 1..=4 { + h.hover_at(from + egui::vec2(0.0, 15.0 * k * i as f32)); + h.run_steps(1); + } + h.drop_at(from + egui::vec2(0.0, 60.0 * k)); + for _ in 0..10 { + h.run_steps(2); + } + assert!(h.state().views[0].block_drag.is_none(), "the drag is over"); + let after = h.state().session.get(id).unwrap().text_blocks(0)[0].rect; + assert!(before[1] - after[1] > 50.0, "the paragraph moved down with it: {before:?} to {after:?}"); +} + #[test] fn dragging_the_rotate_knob_turns_a_page_image() { let mut png = Vec::new();