From 76037a77ac6bc01ff3ed5cdc16017656dbd1526a Mon Sep 17 00:00:00 2001 From: Valentin Klein Date: Thu, 15 Oct 2026 08:35:26 +0200 Subject: [PATCH] View: show page size when hovering the bottom-left of the page view (#1855) (#1979) Co-authored-by: Claude Sonnet 5.5 --- crates/ui-egui/src/canvas.rs | 42 +++++++++++++++++++++++++++++++++ crates/ui-egui/tests/control.rs | 23 ++++++++++++++++++ parity/acrobat-features.toml | 12 ++++++++++ 3 files changed, 77 insertions(+) diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index c19d90c69..f2d2807bf 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -3361,6 +3361,7 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) { }); } find_bar(view, info.pages.len(), avail, ui, &t); + page_size_hint(view, info, units, avail, unobstructed, ui, &t); if tool == QuickTool::EnhanceCamera { crate::camera_ui::bar(ui.ctx(), view, avail, &t, can_modify); } @@ -3709,6 +3710,37 @@ fn set_layers(app: &mut PdfCraftApp, index: usize, changes: &[(LayerOp, (u32, u1 } } +/// Whether `p` lies in the bottom-left quadrant of `area`, where the page size shows (#1855). +fn in_size_hint_zone(area: Rect, p: Pos2) -> bool { + p.x >= area.min.x && p.x < area.center().x && p.y > area.center().y && p.y <= area.max.y +} + +/// The current page's size, floating at the bottom-left of the document area while the pointer is +/// in that quadrant, as Acrobat does. Same formatter and unit as Document Properties. +fn page_size_hint(view: &DocView, info: &DocInfo, units: crate::units::Unit, area: Rect, unobstructed: bool, ui: &mut egui::Ui, t: &Tokens) { + let Some(page) = info.pages.get(view.current) else { return }; + let inside = unobstructed && ui.input(|i| i.pointer.hover_pos()).is_some_and(|p| in_size_hint_zone(area, p)); + if !inside { + return; + } + let text = units.size(f64::from(page.width), f64::from(page.height)); + egui::Area::new(egui::Id::new("page-size-hint")) + .order(egui::Order::Foreground) + .interactable(false) + .pivot(egui::Align2::LEFT_BOTTOM) + .fixed_pos(area.left_bottom() + vec2(14.0, -14.0)) + .show(ui.ctx(), |ui| { + egui::Frame::NONE + .fill(t.card) + .stroke(Stroke::new(1.0, t.border)) + .corner_radius(CornerRadius::same(6)) + .inner_margin(egui::Margin::symmetric(8, 4)) + .show(ui, |ui| { + ui.add(egui::Label::new(egui::RichText::new(text).color(t.text)).selectable(false)); + }); + }); +} + /// Acrobat-style floating find bar at the top-right of the document area. fn find_bar(view: &mut DocView, pages: usize, area: Rect, ui: &mut egui::Ui, t: &Tokens) { let Some(find) = view.find.as_mut() else { return }; @@ -4658,6 +4690,16 @@ pub(crate) fn texture_image(size: [usize; 2], pixels: pdfcraft_render::Pixels) - #[cfg(test)] mod tests { + #[test] + fn page_size_hint_zone_is_the_bottom_left_quadrant() { + let area = Rect::from_min_max(pos2(100.0, 50.0), pos2(500.0, 450.0)); + assert!(in_size_hint_zone(area, pos2(110.0, 440.0))); + assert!(!in_size_hint_zone(area, pos2(110.0, 100.0)), "top-left"); + assert!(!in_size_hint_zone(area, pos2(400.0, 440.0)), "bottom-right"); + assert!(!in_size_hint_zone(area, pos2(50.0, 440.0)), "outside"); + assert!(!in_size_hint_zone(area, pos2(f32::NAN, 440.0))); + } + use super::*; fn annotation(fill_sign: bool) -> pdfcraft_render::Annotation { diff --git a/crates/ui-egui/tests/control.rs b/crates/ui-egui/tests/control.rs index a73b3073b..10df41a50 100644 --- a/crates/ui-egui/tests/control.rs +++ b/crates/ui-egui/tests/control.rs @@ -1576,3 +1576,26 @@ fn click_and_drag_refuse_coordinates_that_overflow_like_move() { assert_eq!(ok(&mut h, &c, "ui.click", json!({ "x": x, "y": y }))["clicked"], json!([x, y])); ok(&mut h, &c, "ui.drag", json!({ "from": [x, y], "to": [x + 10.0, y], "steps": 2 })); } + +#[test] +fn page_size_shows_while_the_pointer_is_in_the_bottom_left_quadrant() { + let (mut h, _c) = harness(); + let area = h.state().views[0].viewport_rect(); + let shown = |h: &Harness<'static, PdfCraftApp>| { + let layer = egui::LayerId::new(egui::Order::Foreground, egui::Id::new("page-size-hint")); + h.ctx.memory(|m| m.areas().visible_last_frame(&layer)) + }; + let away = area.center() + egui::vec2(300.0, -200.0); + h.event(egui::Event::PointerMoved(away)); + h.run_steps(3); + assert!(!shown(&h), "top-right: hidden"); + h.event(egui::Event::PointerMoved(area.left_bottom() + egui::vec2(80.0, -80.0))); + h.run_steps(3); + assert!(shown(&h), "bottom-left: shown"); + if let Ok(dir) = std::env::var("PDFCRAFT_SHOT_DIR") { + h.render().unwrap().save(format!("{dir}/page-size-hint.png")).unwrap(); + } + h.event(egui::Event::PointerMoved(away)); + h.run_steps(3); + assert!(!shown(&h), "pointer left: hidden again"); +} diff --git a/parity/acrobat-features.toml b/parity/acrobat-features.toml index 769dc2529..61537a0ad 100644 --- a/parity/acrobat-features.toml +++ b/parity/acrobat-features.toml @@ -1193,6 +1193,18 @@ tools = [] evidence = ["crates/ui-egui/tests/comments.rs::hovering_a_comment_shows_its_author_and_text"] notes = "a popup with the author and the comment text, as Acrobat shows" +[[feature]] +id = "view.page-size-hover" +title = "Page size on hover (bottom-left of the page view)" +area = "B" +tier = "P2" +milestone = "M3" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/ui-egui/tests/control.rs::page_size_shows_while_the_pointer_is_in_the_bottom_left_quadrant", "crates/ui-egui/src/canvas.rs::page_size_hint_zone_is_the_bottom_left_quadrant"] +notes = "While the pointer is in the bottom-left quadrant of the page view, a small label shows the current page's size (after /Rotate and /UserUnit) in the Preferences unit, formatted as in Document Properties (#1855). It never takes clicks and hides when the pointer leaves. Acrobat's trigger is the corner itself and may differ in exact area and wording (not compared with real Acrobat); the size of the current page only, not of the page under the pointer; no headless tool (page sizes are in document info)." + [[feature]] id = "view.document-tabs" title = "Tabbed documents"