fix(ui): preserve scroll position when collapsing or resizing sidebars (#1056) (#1069)

* fix(ui): preserve scroll position when collapsing or resizing sidebars (#1056)

* Run cargo fmt

---------

Co-authored-by: Brandon Thomas <[email protected]>
This commit is contained in:
Omar EldersandBrandon Thomas authored and GitHub committed 2026-10-11 13:32:38 -04:00
1 parent 6f96c40eaa
commit bed9a48ccd
2 files changed
+83

No files matched your search

+20
View File
@@ -1803,7 +1803,27 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {
let avail = ui.available_rect_before_wrap();
view.viewport_w = avail.width();
view.viewport_h = avail.height();
let old_zoom = view.zoom;
view.fit_zoom(info);
if view.shown && view.zoom_anchor.is_none() && view.goto.is_none() && view.goto_point.is_none() && (view.zoom - old_zoom).abs() > 1e-4 {
let prev_viewport = view.viewport_screen;
let top_y = prev_viewport.min.y;
let anchor = view
.screen_rects
.iter()
.find(|(_, r)| r.min.y <= top_y && r.max.y > top_y)
.map(|(p, r)| {
let fy = if r.height() > 0.0 { ((top_y - r.min.y) / r.height()).clamp(0.0, 1.0) } else { 0.0 };
(*p, fy, 0.0f32)
})
.or_else(|| view.screen_rects.iter().find(|(_, r)| r.min.y >= top_y).map(|(p, r)| (*p, 0.0f32, (r.min.y - top_y).max(0.0))))
.or_else(|| view.screen_rects.last().map(|(p, r)| (*p, 1.0f32, (r.max.y - top_y).max(0.0))));
if let Some((page, fy, rel_y)) = anchor {
view.zoom_anchor = Some((page, 0.5, fy, vec2(avail.width() / 2.0, rel_y)));
} else {
view.zoom_anchor = Some((view.current, 0.5, 0.0, vec2(avail.width() / 2.0, MARGIN)));
}
}
// Pinch / ctrl+scroll zoom, anchored on the current page.
let zoom_delta = ui.input(|i| i.zoom_delta());
+63
View File
@@ -920,3 +920,66 @@ trailer << /Root 1 0 R >>
let first = detail(&mut h);
assert!(first * 10 > sharp * 7, "the first frame after zooming lost its detail: {first} against {sharp}");
}
#[test]
fn sidebar_collapse_and_resize_preserves_scroll_position() {
use pdfcraft_ui_egui::canvas::Fit;
let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).build_eframe(|_cc| {
let mut app = PdfCraftApp::new();
app.set_option("language", "en").unwrap();
app.open_bytes("pages.pdf", None, PAGES.to_vec()).expect("opens");
app.set_option("left", "closed").unwrap();
app.set_option("panel", "bookmarks").unwrap();
app.set_option("layout", "continuous").unwrap();
app
});
let mut width = 0.0;
for _ in 0..400 {
h.run_steps(1);
let w = h.state().views[0].viewport_rect().width();
if w == width {
break;
}
width = w;
}
h.state_mut().views[0].go_to_page(2);
h.run_steps(6);
let v = &h.state().views[0];
assert_eq!(v.current, 2);
assert_eq!(v.fit, Fit::Width);
let zoom_before = v.zoom;
let rect_before = v.page_screen_rect(2).expect("page 2 on screen");
let vp_before = v.viewport_rect();
let rel_y_before = rect_before.min.y - vp_before.min.y;
// Collapse bookmarks panel: available width increases, so fit-width zoom increases.
h.state_mut().set_option("panel", "none").unwrap();
h.run_steps(6);
let v = &h.state().views[0];
let zoom_after = v.zoom;
assert!(zoom_after > zoom_before, "zoom increased when panel collapsed: {zoom_after} vs {zoom_before}");
assert_eq!(v.current, 2, "still on page 2 after panel collapsed");
let rect_after = v.page_screen_rect(2).expect("page 2 still on screen");
let vp_after = v.viewport_rect();
let rel_y_after = rect_after.min.y - vp_after.min.y;
assert!((rel_y_after - rel_y_before).abs() < 1.0, "page 2 scroll position preserved: before {rel_y_before}, after {rel_y_after}");
// Reopen bookmarks panel: available width decreases, so fit-width zoom decreases.
h.state_mut().set_option("panel", "bookmarks").unwrap();
for _ in 0..400 {
h.run_steps(1);
let w = h.state().views[0].viewport_rect().width();
if w == width {
break;
}
width = w;
}
let v = &h.state().views[0];
assert_eq!(v.current, 2, "still on page 2 after panel reopened");
let rect_reopened = v.page_screen_rect(2).expect("page 2 still on screen");
let vp_reopened = v.viewport_rect();
let rel_y_reopened = rect_reopened.min.y - vp_reopened.min.y;
assert!((rel_y_reopened - rel_y_before).abs() < 1.0, "page 2 scroll position preserved on reopen: before {rel_y_before}, after {rel_y_reopened}");
}