From 1b3783b79541f62ee5030c84adf20905703f71ec Mon Sep 17 00:00:00 2001 From: burnintuna Date: Wed, 22 Jul 2026 03:10:57 +0900 Subject: [PATCH] Fix continuous page seeking --- src/viewer.rs | 56 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/src/viewer.rs b/src/viewer.rs index 47904e8..6e601d6 100644 --- a/src/viewer.rs +++ b/src/viewer.rs @@ -243,6 +243,7 @@ impl ViewerWindow { navigation_message, navigation_message_label, navigation_message_generation: Rc::new(Cell::new(0)), + continuous_programmatic_scroll: Cell::new(false), state: RefCell::new(ViewerState { pages: Vec::new(), anchor: 0, @@ -333,6 +334,7 @@ struct ViewerController { navigation_message: gtk::Box, navigation_message_label: gtk::Label, navigation_message_generation: Rc>, + continuous_programmatic_scroll: Cell, state: RefCell, } @@ -948,13 +950,16 @@ impl ViewerController { } fn continuous_scrolled(self: &Rc) { - if self.view_mode() != ViewMode::Continuous || self.state.borrow().pages.is_empty() { + let pages_empty = self.state.borrow().pages.is_empty(); + if self.view_mode() != ViewMode::Continuous || pages_empty { return; } let visible = self.visible_indices(); let new_anchor = visible.first().copied(); - let changed = new_anchor.is_some_and(|index| index != self.state.borrow().anchor); - if let Some(index) = new_anchor { + let update_anchor = !self.continuous_programmatic_scroll.get(); + let changed = + update_anchor && new_anchor.is_some_and(|index| index != self.state.borrow().anchor); + if let Some(index) = new_anchor.filter(|_| update_anchor) { self.state.borrow_mut().anchor = index; } self.render_continuous(&visible); @@ -1014,16 +1019,28 @@ impl ViewerController { } fn scroll_to_anchor(&self) { - let state = self.state.borrow(); - let Some(row) = state.continuous_rows.get(state.anchor) else { - return; + let frame = { + let state = self.state.borrow(); + let Some(row) = state.continuous_rows.get(state.anchor) else { + return; + }; + row.frame.clone() }; - let Some(bounds) = row.frame.compute_bounds(&self.continuous_box) else { + let Some(bounds) = frame.compute_bounds(&self.continuous_box) else { return; }; let adjustment = self.continuous_scrolled.vadjustment(); let maximum = (adjustment.upper() - adjustment.page_size()).max(adjustment.lower()); - adjustment.set_value(f64::from(bounds.y()).clamp(adjustment.lower(), maximum)); + let target = centered_scroll_value( + f64::from(bounds.y()), + f64::from(bounds.height()), + adjustment.page_size(), + adjustment.lower(), + maximum, + ); + self.continuous_programmatic_scroll.set(true); + adjustment.set_value(target); + self.continuous_programmatic_scroll.set(false); } fn clear_continuous_paintables(&self) { @@ -1629,6 +1646,16 @@ fn intersecting_rows(bounds: &[Option<(f64, f64)>], top: f64, bottom: f64) -> Ve .collect() } +fn centered_scroll_value( + row_top: f64, + row_height: f64, + viewport_height: f64, + minimum: f64, + maximum: f64, +) -> f64 { + (row_top + row_height / 2.0 - viewport_height / 2.0).clamp(minimum, maximum) +} + fn page_seek_index(value: f64, count: usize) -> Option { (count > 0).then(|| (value.round().max(0.0) as usize).min(count - 1)) } @@ -1673,6 +1700,19 @@ mod tests { assert_eq!(intersecting_rows(&bounds, 3000.0, 3800.0), [2]); } + #[test] + fn continuous_seek_centers_the_selected_row() { + assert_eq!( + centered_scroll_value(1_000.0, 800.0, 600.0, 0.0, 5_000.0), + 1_100.0 + ); + assert_eq!(centered_scroll_value(0.0, 400.0, 800.0, 0.0, 5_000.0), 0.0); + assert_eq!( + centered_scroll_value(5_000.0, 800.0, 600.0, 0.0, 5_000.0), + 5_000.0 + ); + } + #[test] fn fast_loading_prefetches_a_larger_window() { assert_eq!(prefetch_window(&[10], 30, false), [10, 11, 12, 9]);