diff --git a/crates/ui-egui/src/forms_ui.rs b/crates/ui-egui/src/forms_ui.rs index 563bb238ce..890aeb95ce 100644 --- a/crates/ui-egui/src/forms_ui.rs +++ b/crates/ui-egui/src/forms_ui.rs @@ -162,6 +162,11 @@ pub(crate) fn page_input( .map(|(i, w)| (f, i, w)) }); let Some((f, wi, w)) = hit else { return FieldPointer::Away }; + // The field's tooltip (`/TU`), as Acrobat shows it on hover (#3854). ISO 32000-2 Table 226 + // makes it the name the user interface identifies the field by. + if let Some(tip) = f.tooltip.as_deref().filter(|t| !t.trim().is_empty()) { + resp.clone().on_hover_text_at_pointer(tip); + } // Empty signature fields are signed by clicking them (Use a certificate); push buttons run // their action. let pressable = f.kind == FormFieldKind::PushButton && !f.read_only() && f.button.is_some(); diff --git a/crates/ui-egui/tests/forms.rs b/crates/ui-egui/tests/forms.rs index 42c6285f01..6161d71ce4 100644 --- a/crates/ui-egui/tests/forms.rs +++ b/crates/ui-egui/tests/forms.rs @@ -1,6 +1,7 @@ //! Filling in a form in the real shell (egui_kittest): typing, Tab, check boxes, radios, choices. use egui_kittest::Harness; +use egui_kittest::kittest::Queryable; use pdfcraft_ui_egui::PdfCraftApp; fn harness() -> Harness<'static, PdfCraftApp> { @@ -44,6 +45,62 @@ fn click_field(h: &mut Harness<'static, PdfCraftApp>, name: &str, widget: usize) h.run_steps(3); } +/// Rest the pointer on the centre of a field's widget long enough for a tooltip. +fn rest_on_field(h: &mut Harness<'static, PdfCraftApp>, name: &str) { + let p = { + let s = h.state(); + let doc = s.session.get(s.views[0].id).unwrap(); + let f = doc.form.iter().find(|f| f.name == name).unwrap(); + pdfcraft_ui_egui::forms_ui::field_screen_rect(&s.views[0], &doc.info, f, 0).expect("on screen").center() + }; + h.hover_at(p); + for _ in 0..40 { + h.step(); + std::thread::sleep(std::time::Duration::from_millis(15)); + } +} + +/// #3854: resting on a field shows its tooltip (`/TU`); a field without one shows none. +#[test] +fn hovering_a_field_shows_its_tooltip() { + let mut h = harness(); + assert!(h.query_by_label("Your name").is_none(), "no tooltip before the hover"); + rest_on_field(&mut h, "name"); + assert!(h.query_by_label("Your name").is_some(), "the name field's /TU shows on hover"); + rest_on_field(&mut h, "city"); + assert!(h.query_by_label("Your name").is_none(), "another field doesn't keep the tooltip"); + // Nor shows one of its own: none has been shown since the pointer left `name` (well past the + // tooltip delay), so this isn't only the old label going away. + assert!(egui::Tooltip::seconds_since_last_tooltip(&h.ctx) > 0.3, "a field without /TU shows no tooltip"); +} + +/// Give the `city` field these properties, as the Properties dialog would. +fn set_city(h: &mut Harness<'static, PdfCraftApp>, props: pdfcraft_engine::FieldProps) { + h.state_mut().apply_edit(pdfcraft_engine::Edit::SetFieldProps { name: "city".into(), props: Box::new(props) }); + h.run_steps(3); +} + +/// A read-only field still shows its tooltip: it can't be filled, but it can be identified. +#[test] +fn a_read_only_fields_tooltip_shows_on_hover() { + let mut h = harness(); + set_city(&mut h, pdfcraft_engine::FieldProps { tooltip: Some("Where you live".into()), read_only: Some(true), ..Default::default() }); + rest_on_field(&mut h, "city"); + assert!(h.query_by_label("Where you live").is_some()); +} + +/// A `/TU` of only spaces shows no tooltip (no empty box under the pointer). +#[test] +fn a_blank_tooltip_shows_nothing() { + let mut h = harness(); + set_city(&mut h, pdfcraft_engine::FieldProps { tooltip: Some(" ".into()), ..Default::default() }); + let s = h.state(); + let doc = s.session.get(s.views[0].id).unwrap(); + assert_eq!(doc.form.iter().find(|f| f.name == "city").unwrap().tooltip.as_deref(), Some(" "), "the blank /TU is in the file"); + rest_on_field(&mut h, "city"); + assert!(egui::Tooltip::seconds_since_last_tooltip(&h.ctx).is_infinite(), "no tooltip was shown"); +} + #[test] fn typing_into_a_text_field_and_tabbing_on() { let mut h = harness(); diff --git a/parity/acrobat-features.toml b/parity/acrobat-features.toml index dde0524028..a54802b574 100644 --- a/parity/acrobat-features.toml +++ b/parity/acrobat-features.toml @@ -4416,8 +4416,8 @@ milestone = "M6" status = "shipped" commands = ["form.fields"] tools = ["form_fill", "form_fields"] -evidence = ["crates/ui-egui/tests/forms.rs::typing_into_a_text_field_and_tabbing_on", "crates/ui-egui/tests/forms.rs::auto_size_fields_shrink_and_wrap_typed_text_to_fit_their_box", "crates/forms/src/tests.rs::text_fields_get_new_appearances", "crates/engine/src/tests.rs::filling_a_form_shows_saves_and_undoes", "crates/automation/tests/automation.rs::orphan_form_fields_through_tools", "crates/forms/src/tests.rs::fields_sharing_a_name_take_the_same_value"] -notes = "in-place editor; Enter or clicking away commits, Escape cancels; auto-size (0 Tf) fields shrink, and multiline ones wrap, the text being typed to fit the box, on screen as on the page (the screen measures in the interface font, so its size can be a little smaller than the page's Helvetica fit); fixed sizes are kept and clip, as in Acrobat; no format/keystroke JavaScript yet (M6.4)" +evidence = ["crates/ui-egui/tests/forms.rs::typing_into_a_text_field_and_tabbing_on", "crates/ui-egui/tests/forms.rs::auto_size_fields_shrink_and_wrap_typed_text_to_fit_their_box", "crates/forms/src/tests.rs::text_fields_get_new_appearances", "crates/engine/src/tests.rs::filling_a_form_shows_saves_and_undoes", "crates/automation/tests/automation.rs::orphan_form_fields_through_tools", "crates/forms/src/tests.rs::fields_sharing_a_name_take_the_same_value", "crates/ui-egui/tests/forms.rs::hovering_a_field_shows_its_tooltip"] +notes = "in-place editor; Enter or clicking away commits, Escape cancels; auto-size (0 Tf) fields shrink, and multiline ones wrap, the text being typed to fit the box, on screen as on the page (the screen measures in the interface font, so its size can be a little smaller than the page's Helvetica fit); fixed sizes are kept and clip, as in Acrobat; no format/keystroke JavaScript yet (M6.4); resting the pointer on any field shows its tooltip (/TU), as Acrobat does (#3854)" [[feature]] id = "form.fill-checkbox-radio"