diff --git a/crates/ui-egui/src/content_ui.rs b/crates/ui-egui/src/content_ui.rs index a6475543f5..d9bc21d025 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 3efaecacc7..031d9b3fb2 100644 --- a/crates/ui-egui/src/edit_text_ui.rs +++ b/crates/ui-egui/src/edit_text_ui.rs @@ -1531,9 +1531,12 @@ pub(crate) fn overlay(ctx: &egui::Context, view: &mut DocView, info: &DocInfo) - let mut done = None; let mut reveal = false; let mut look = 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. @@ -1626,8 +1629,7 @@ pub(crate) fn overlay(ctx: &egui::Context, view: &mut DocView, info: &DocInfo) - done = Some(true); } }); - }, - ); + }); if reveal { view.text_background.reveal(); } 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 27c97a240e..60df5bc192 100644 --- a/crates/ui-egui/src/forms_ui.rs +++ b/crates/ui-egui/src/forms_ui.rs @@ -503,7 +503,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 78ceeb5ce9..942d789d19 100644 --- a/crates/ui-egui/tests/editing.rs +++ b/crates/ui-egui/tests/editing.rs @@ -2996,6 +2996,62 @@ fn a_paragraph_whose_edit_fails_stays_open_with_its_typing() { assert_eq!(doc.text_blocks(0).iter().map(|b| b.text.clone()).collect::>(), ["Top edited", "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 accent = egui::Color32::from_rgb(0x14, 0x73, 0xE6); + for frame in 0..8 { + h.step(); + // 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"); + } +} + #[test] fn editing_existing_text_in_place() { let mut h = harness(1, |_| {});