From 68c85bce6eb00d364c3f0a872129515215efe2af Mon Sep 17 00:00:00 2001 From: komo1iddin <45395275+komo1iddin@users.noreply.github.com> Date: Wed, 21 Oct 2026 23:06:23 +0800 Subject: [PATCH 1/2] M7: open the paragraph editor without a fade-in The paragraph editor is an egui Area, and Areas fade in over the style's animation time (0.2 s) each time they appear. While the editor faded, so did its cover and its frame: the page's own copy of the paragraph showed through it under the editor's text, and the frame pulsed from dim to full. Every click into a paragraph did this. Show the in-place editors at once: the paragraph editor, the editor for text added in this session, the Fill & Sign text box and the form field editor. Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/content_ui.rs | 4 +- crates/ui-egui/src/edit_text_ui.rs | 12 +++--- crates/ui-egui/src/fill_sign.rs | 4 +- crates/ui-egui/src/forms_ui.rs | 4 +- crates/ui-egui/tests/editing.rs | 59 ++++++++++++++++++++++++++++++ 5 files changed, 75 insertions(+), 8 deletions(-) diff --git a/crates/ui-egui/src/content_ui.rs b/crates/ui-egui/src/content_ui.rs index 5f5ef32f77..d024599073 100644 --- a/crates/ui-egui/src/content_ui.rs +++ b/crates/ui-egui/src/content_ui.rs @@ -523,7 +523,9 @@ pub(crate) fn editor(ctx: &egui::Context, view: &mut DocView, info: &DocInfo, ad let (mut commit, mut cancel, mut done) = (false, false, false); let mut undrawable = None; let memo = &view.content.undrawable; - egui::Area::new(egui::Id::new(("added-text", view.id.0))).order(egui::Order::Foreground).fixed_pos(r.min).constrain(false).show(ctx, |ui| { + // Shown at once, like the paragraph editor: a fade-in shows the page's copy through it. + let area = egui::Area::new(egui::Id::new(("added-text", view.id.0))).fade_in(false); + area.order(egui::Order::Foreground).fixed_pos(r.min).constrain(false).show(ctx, |ui| { let Some(t) = view.content.draft.as_mut() else { return }; ui.set_width(r.width() + 64.0); if let Some(source_rect) = source_rect.filter(|_| !background_ready) { diff --git a/crates/ui-egui/src/edit_text_ui.rs b/crates/ui-egui/src/edit_text_ui.rs index 66ad98ffe6..75cbd03e1d 100644 --- a/crates/ui-egui/src/edit_text_ui.rs +++ b/crates/ui-egui/src/edit_text_ui.rs @@ -1337,9 +1337,12 @@ pub(crate) fn overlay(ctx: &egui::Context, view: &mut DocView, info: &DocInfo) - let font = editor_font(&ed.look, smallest.map_or(ed.size, |s| (s * scale * 0.5).clamp(1.0, 300.0).min(ed.size))); let text_color = color32(ed.look.color); let mut done = None; - egui::Area::new(egui::Id::new("edit-text-line")).order(egui::Order::Foreground).fixed_pos(Pos2::new(ed.rect.left(), ed.rect.top())).show( - ctx, - |ui| { + // No fade-in: while it faded, the page's own copy of the text showed through the cover. + egui::Area::new(egui::Id::new("edit-text-line")) + .order(egui::Order::Foreground) + .fade_in(false) + .fixed_pos(Pos2::new(ed.rect.left(), ed.rect.top())) + .show(ctx, |ui| { // The editor opens at the source size. Once text changes, show the real draft // extent: single rows grow toward the page edge, paragraphs rewrap at their // source width, and the visible frame grows/shrinks vertically with their rows. @@ -1417,8 +1420,7 @@ pub(crate) fn overlay(ctx: &egui::Context, view: &mut DocView, info: &DocInfo) - done = Some(true); } }); - }, - ); + }); match done { Some(apply) => { let ed = view.line_editor.take()?; diff --git a/crates/ui-egui/src/fill_sign.rs b/crates/ui-egui/src/fill_sign.rs index 794fe0bf1b..39c6872437 100644 --- a/crates/ui-egui/src/fill_sign.rs +++ b/crates/ui-egui/src/fill_sign.rs @@ -706,7 +706,9 @@ pub(crate) fn type_box(ctx: &egui::Context, view: &mut DocView, info: &DocInfo, let zoom = xf.rect.width() / xf.pw.max(1.0); let mut commit = false; let mut cancel = false; - egui::Area::new(egui::Id::new(("fill-text", view.id.0))).order(egui::Order::Foreground).fixed_pos(pos).show(ctx, |ui| { + // Shown at once, like the paragraph editor: a fade-in shows the page's copy through it. + let area = egui::Area::new(egui::Id::new(("fill-text", view.id.0))).fade_in(false); + area.order(egui::Order::Foreground).fixed_pos(pos).show(ctx, |ui| { let Some(t) = view.fill_text.as_mut() else { return }; // The box is white in every theme, so the caret must not follow the theme (pale in dark mode) (#655). ui.visuals_mut().text_cursor.stroke.color = crate::edit_text_ui::caret_color_on(Color32::WHITE); diff --git a/crates/ui-egui/src/forms_ui.rs b/crates/ui-egui/src/forms_ui.rs index 612b985e65..c00261778b 100644 --- a/crates/ui-egui/src/forms_ui.rs +++ b/crates/ui-egui/src/forms_ui.rs @@ -497,7 +497,9 @@ pub(crate) fn overlay(ctx: &egui::Context, view: &mut DocView, info: &DocInfo, f match f.kind { FormFieldKind::Text => { let multiline = f.has(field_flags::MULTILINE); - egui::Area::new(egui::Id::new(("form-editor", view.id.0))).order(egui::Order::Foreground).fixed_pos(rect.min).show(ctx, |ui| { + // Shown at once, like the paragraph editor: a fade-in shows the field's value through it. + let area = egui::Area::new(egui::Id::new(("form-editor", view.id.0))).fade_in(false); + area.order(egui::Order::Foreground).fixed_pos(rect.min).show(ctx, |ui| { ui.set_min_size(rect.size()); let Some(fx) = view.forms.focus.as_mut() else { return }; // Sized from the text being typed, so an auto-sized field shrinks to keep it in view. diff --git a/crates/ui-egui/tests/editing.rs b/crates/ui-egui/tests/editing.rs index d41041e52e..cb74aada9d 100644 --- a/crates/ui-egui/tests/editing.rs +++ b/crates/ui-egui/tests/editing.rs @@ -2735,6 +2735,65 @@ fn switching_straight_to_another_box_applies_the_open_paragraph() { assert_eq!(ed.text, "Bottom"); } +/// Two boxes, "Top" and "Bottom", in Edit text, with egui's animations on: the window animates +/// unless the system asks for less motion, and the test harness turns them off. +fn two_boxes_animated() -> (Harness<'static, PdfCraftApp>, [egui::Rect; 2]) { + let bytes = source_font_fixture_with_text("BT /F1 24 Tf 20 220 Td (Top) Tj ET BT /F2 24 Tf 20 120 Td (Bottom) Tj ET"); + let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).with_step_dt(1.0 / 60.0).build_eframe(move |_cc| { + let mut app = PdfCraftApp::new(); + app.set_option("language", "en").unwrap(); + app.open_bytes("boxes.pdf", None, bytes).expect("opens"); + app + }); + h.ctx.all_styles_mut(|s| s.animation_time = 0.2); + h.run_steps(4); + assert!(h.state_mut().execute("edit.edit_text")); + h.run_steps(2); + let r = h.state().views[0].page_screen_rect(0).expect("on screen"); + let k = r.width() / 300.0; + let mut blocks = { + let s = h.state(); + s.session.get(s.views[0].id).unwrap().text_blocks(0) + }; + blocks.sort_by(|a, b| b.rect[1].partial_cmp(&a.rect[1]).unwrap_or(std::cmp::Ordering::Equal)); + let on_screen = |b: &pdfcraft_engine::TextBlock| { + egui::Rect::from_min_max( + egui::pos2(r.left() + b.rect[0] as f32 * k, r.top() + (300.0 - b.rect[3] as f32) * k), + egui::pos2(r.left() + b.rect[2] as f32 * k, r.top() + (300.0 - b.rect[1] as f32) * k), + ) + }; + (h, [on_screen(&blocks[0]), on_screen(&blocks[1])]) +} + +/// The colour of the editor's frame (the accent stroke around `r`) this frame, if it is painted. +fn editor_frame_color(h: &Harness<'static, PdfCraftApp>, r: egui::Rect) -> Option { + h.output().shapes.iter().find_map(|shape| match &shape.shape { + egui::Shape::Rect(rect) if rect.stroke.width == 1.5 && rect.rect.contains(r.center()) => Some(rect.stroke.color), + _ => None, + }) +} + +/// A paragraph opens at once: it used to fade in over a fifth of a second, its frame dimmed and +/// the page's own copy of the text showing through the editor's cover. +#[test] +fn a_paragraph_editor_opens_at_full_opacity() { + let (mut h, [top, _]) = two_boxes_animated(); + h.hover_at(top.center()); + h.run_steps(1); + h.drag_at(top.center()); + h.run_steps(1); + h.drop_at(top.center()); + let mut seen = 0; + for frame in 0..8 { + h.step(); + if let Some(color) = editor_frame_color(&h, top) { + assert_eq!(color, egui::Color32::from_rgb(0x14, 0x73, 0xE6), "frame {frame} after the click draws a faded frame"); + seen += 1; + } + } + assert!(seen > 0, "the editor is drawn"); +} + #[test] fn editing_existing_text_in_place() { let mut h = harness(1, |_| {}); From b5ba1760233a1c408e5525ec629090c1c070dd88 Mon Sep 17 00:00:00 2001 From: komo1iddin <45395275+komo1iddin@users.noreply.github.com> Date: Thu, 22 Oct 2026 00:09:21 +0800 Subject: [PATCH 2/2] test: the paragraph editor is drawn at full colour from the click's own frame Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/tests/editing.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/ui-egui/tests/editing.rs b/crates/ui-egui/tests/editing.rs index cb74aada9d..5686676196 100644 --- a/crates/ui-egui/tests/editing.rs +++ b/crates/ui-egui/tests/editing.rs @@ -2783,15 +2783,12 @@ fn a_paragraph_editor_opens_at_full_opacity() { h.drag_at(top.center()); h.run_steps(1); h.drop_at(top.center()); - let mut seen = 0; + let accent = egui::Color32::from_rgb(0x14, 0x73, 0xE6); for frame in 0..8 { h.step(); - if let Some(color) = editor_frame_color(&h, top) { - assert_eq!(color, egui::Color32::from_rgb(0x14, 0x73, 0xE6), "frame {frame} after the click draws a faded frame"); - seen += 1; - } + // The click's own frame opens it: the editor's frame is there from then on, at full colour. + assert_eq!(editor_frame_color(&h, top), Some(accent), "frame {frame} after the click"); } - assert!(seen > 0, "the editor is drawn"); } #[test]