mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-29 20:47:32 -06:00
Merge PR #2587: M3: single-page view turns pages without a blank frame
This commit is contained in:
@@ -2410,9 +2410,10 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {
|
||||
// When not scrolling continuously, only one row is shown at a time: the current page, or the
|
||||
// current two-page spread (with the cover rule). These indices drive paging, which pages are
|
||||
// drawn, and the content height (#752). Empty for a document with no pages.
|
||||
let row: Vec<usize> = if rects.is_empty() {
|
||||
Vec::new()
|
||||
} else {
|
||||
let shown_row = |view: &DocView| -> Vec<usize> {
|
||||
if rects.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
let last = rects.len() - 1;
|
||||
let cur = view.current.min(last);
|
||||
match view.layout {
|
||||
@@ -2424,6 +2425,7 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {
|
||||
}
|
||||
}
|
||||
};
|
||||
let row = shown_row(view);
|
||||
let middle_gesture = view.auto_scroll.blocks_input();
|
||||
// Not scrolling: when the whole page or spread fits, the wheel would do nothing, so it turns
|
||||
// pages instead; zoomed in far enough to pan, it pans, and at the edge it turns the page on
|
||||
@@ -2524,6 +2526,9 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {
|
||||
if view.fling.gliding() {
|
||||
ui.ctx().request_repaint();
|
||||
}
|
||||
// The wheel may have turned the page above: draw the row it turned to. Measured from the old
|
||||
// row, the jump to the new page scrolled both out of view, a blank frame on every turn forward.
|
||||
let row = shown_row(view);
|
||||
let visible_pages: Vec<usize> = if view.continuous { (0..rects.len()).collect() } else { row.clone() };
|
||||
let (y_shift, content_h) = if view.continuous {
|
||||
(0.0, rects.iter().map(|r| r.bottom()).reduce(f32::max).map(|bottom| bottom + MARGIN).unwrap_or(0.0))
|
||||
@@ -2563,9 +2568,6 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {
|
||||
let r = rects[page.min(rects.len() - 1)];
|
||||
let point = pos2(r.left() + fx * r.width(), r.top() - y_shift + fy * r.height());
|
||||
scroll = scroll.scroll_offset(vec2((point.x - rel.x).max(0.0), (point.y - rel.y).max(0.0)));
|
||||
} else if edge_turned {
|
||||
// The row above was laid out for the page before the turn: place the new one next frame.
|
||||
ui.ctx().request_repaint();
|
||||
} else if let Some((page, frac)) = view.goto.take() {
|
||||
let page = page.min(rects.len() - 1);
|
||||
let r = rects[page];
|
||||
@@ -3612,6 +3614,26 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {
|
||||
view.last_scroll = scroll_offset;
|
||||
let pan_held = now_settle < view.pan_hold_until;
|
||||
let (mut wanted, visible_now) = out.inner;
|
||||
// Single-page and two-page views draw only the current row, so the next one was first asked
|
||||
// for on the turn and showed blank paper until it rendered. Prefetch the rows either side,
|
||||
// as continuous scrolling does for the pages just off screen.
|
||||
if !view.continuous
|
||||
&& let (Some(&first), Some(&last)) = (visible_pages.first(), visible_pages.last())
|
||||
{
|
||||
let n = if view.layout == PageLayout::Single { 1 } else { 2 };
|
||||
let max_side = ui.ctx().input(|inp| inp.max_texture_side) as f32;
|
||||
for i in (last.saturating_add(1)..=last.saturating_add(n)).chain(first.saturating_sub(n)..first) {
|
||||
let Some(p) = info.pages.get(i).filter(|_| !view.errors.contains_key(&i)) else { continue };
|
||||
// As drawn: the whole page at this scale, or the backdrop of a tiled page.
|
||||
let side = p.width.max(p.height).max(1.0);
|
||||
wanted.push(if side * scale > TILE_THRESHOLD.min(max_side) {
|
||||
let bs = BASE_SIDE.min(max_side) / side;
|
||||
(i, bs, scale_tag(bs), None)
|
||||
} else {
|
||||
(i, scale, tag, None)
|
||||
});
|
||||
}
|
||||
}
|
||||
let cur = view.current;
|
||||
wanted.sort_by_key(|w| (!visible_now.contains(&w.0), w.0.abs_diff(cur), w.3.is_some()));
|
||||
// Tiles of far-away pages are useless: free them. Tiles of an earlier zoom stay for a page
|
||||
|
||||
@@ -185,6 +185,38 @@ fn single_page_layout_shows_one_page_at_a_time() {
|
||||
assert!(rect(&h, 0).is_none() && rect(&h, 1).is_some(), "only page 2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_page_view_prefetches_the_next_page() {
|
||||
// Only the shown page is drawn, but the next one renders ahead of the turn: otherwise
|
||||
// turning forward shows blank paper until it arrives.
|
||||
let mut h = harness(&[("layout", "single"), ("zoom", "50")]);
|
||||
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10);
|
||||
while h.state().render_pending() {
|
||||
assert!(std::time::Instant::now() < deadline, "renders settle");
|
||||
h.run_steps(2);
|
||||
std::thread::sleep(std::time::Duration::from_millis(5));
|
||||
}
|
||||
assert!(rect(&h, 1).is_none(), "page 2 is not shown");
|
||||
assert!(h.state().views[0].raster_memory().page_count >= 2, "page 2 is rendered ahead of the turn");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_page_wheel_turn_draws_the_new_page_at_once() {
|
||||
// The frame that turns the page draws the new one. Its jump used to be measured from the
|
||||
// old page's place, which scrolled both out of view: a blank frame on every turn forward.
|
||||
let mut h = harness_stepping(1.0 / 60.0, &[("layout", "single")]);
|
||||
h.key_press_modifiers(Modifiers::COMMAND, Key::Num0); // fit page
|
||||
h.run_steps(4);
|
||||
let vp = h.state().views[0].viewport_rect();
|
||||
h.hover_at(vp.center());
|
||||
h.run_steps(2);
|
||||
h.event(wheel(MouseWheelUnit::Line, -1.0, TouchPhase::Move));
|
||||
h.step();
|
||||
assert_eq!(h.state().views[0].current, 1, "one notch turns one page");
|
||||
let r = rect(&h, 1).expect("page 2 drawn in the frame that turns to it");
|
||||
assert!(r.min.y >= vp.min.y && r.max.y <= vp.max.y, "page 2 fits on screen: {r:?} in {vp:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_page_wheel_pans_instead_when_zoomed_in() {
|
||||
// The zoomed-in guard: with room to pan, the wheel pans and must not turn pages.
|
||||
|
||||
Reference in New Issue
Block a user