mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-29 04:28:20 -06:00
* View: in Single Page view the wheel turns the page at the edge of a zoomed-in page (#1924) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * ROADMAP: log edge paging (#1924) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * View: no jump past the last/first page, a turned page starts at its edge (#1924) step_page is a no-op at either end (it re-aligned the view via goto), the page-view ScrollArea is not animated (its zero-length offset target overwrote the turn's jump), and the rest of a gesture that turned the page no longer pans the new page. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
Brandon Thomas
parent
16829991fe
commit
bb2b03f263
@@ -164,6 +164,7 @@ Ranked; detail and the full milestone table in [docs/roadmap.md](docs/roadmap.md
|
||||
- **2026-10-13 (M5, signature pad undo):** The Draw tab of Create signature/initials has an Undo button and Ctrl/Cmd+Z that take back the last stroke (#1963). Pad is UI-only, so no automation tool. Estimates unchanged.
|
||||
- **2026-10-13 (M2, raw crop and page-unit floor):** The renderer applies its existing one-point minimum in physical points for non-nearly-zero raw crop dimensions on scaled pages. Five synthetic regressions cover inspection, crop origins and rotations, reciprocal rounding and bounded raster controls. Existing UserUnit policy, nearly-zero fallback, feature statuses and estimates are unchanged.
|
||||
- **2026-10-13 (M5, text box font size, #1908):** Comment Properties has a Font size control for text boxes, typewriter text and callouts (1-400 pt); it writes `/DA` and the rich-text strings, redraws the appearance and grows the box to fit. `comment_edit` takes `font_size`. Annot, tool and UI regressions. Estimates unchanged.
|
||||
- **2026-10-13 (M3, Single Page edge paging, #1924):** In Single Page view a page taller than the window pans with the wheel, and one more notch (or a fresh trackpad swipe) at the bottom/top edge shows the next page at its top / the previous page at its bottom. A swipe that panned to the edge, momentum included, does not turn. Estimates unchanged.
|
||||
- **2026-10-13 (M3, native-opening candidate):** Chosen tools survive preparation, conversion and password retries; startup intake waits for pending preparation. Regression tests cover completion, cancellation and document identity. Estimates unchanged.
|
||||
- **2026-10-13 (community, M3, #531 touchpad momentum):** On Wayland a two-finger swipe keeps gliding after the fingers lift and slows to a stop, measured from the gesture's last 0.1 s and decaying with a 0.35 s time constant through the #759 1:1 path. Any wheel input, click, key, zoom or edge ends it (fingers resting on the touchpad don't yet: the vendored winit binds no Wayland hold gestures); single-page paging, macOS (which sends its own momentum), the web, and Animation effects Off never glide. Unit tests for the physics and two headless view tests. Estimates unchanged.
|
||||
- **2026-10-13 (M3, native-open admission):** Later prepared replies stay queued while a modal or native picker owns the foreground. Picker completion, cancellation and abandonment wake retained opens. Regressions cover recipient ownership, exactly-once admission, source/history preservation and one-shot repaint requests. Estimates unchanged.
|
||||
|
||||
@@ -1135,20 +1135,27 @@ impl DocView {
|
||||
/// page of the spread is already on screen, so stepping to it wouldn't move the view (#70).
|
||||
pub fn step_page(&mut self, forward: bool) {
|
||||
let c = self.current;
|
||||
let target = match self.layout {
|
||||
let last = self.page_count.saturating_sub(1);
|
||||
let (target, first) = match self.layout {
|
||||
PageLayout::TwoUp => {
|
||||
// Spreads are [0, 1], [2, 3], … or, with a cover page, [0], [1, 2], [3, 4], ….
|
||||
let first = if self.cover { c.saturating_sub((c + 1) % 2) } else { c - c % 2 };
|
||||
match (forward, self.cover && c == 0) {
|
||||
let target = match (forward, self.cover && c == 0) {
|
||||
(true, true) => 1,
|
||||
(true, false) => first.saturating_add(2),
|
||||
(false, _) if self.cover && first <= 1 => 0,
|
||||
(false, _) => first.saturating_sub(2),
|
||||
}
|
||||
};
|
||||
(target, first)
|
||||
}
|
||||
PageLayout::Single if forward => c + 1,
|
||||
PageLayout::Single => c.saturating_sub(1),
|
||||
PageLayout::Single if forward => (c + 1, c),
|
||||
PageLayout::Single => (c.saturating_sub(1), c),
|
||||
};
|
||||
// Past either end there is nowhere to go: stay put rather than re-align the view, which
|
||||
// would scroll the page back to its top.
|
||||
if if forward { target > last } else { first == 0 } {
|
||||
return;
|
||||
}
|
||||
self.go_to_page(target);
|
||||
}
|
||||
|
||||
@@ -1284,6 +1291,33 @@ impl DocView {
|
||||
self.current != before
|
||||
}
|
||||
|
||||
/// One wheel event in a non-continuous view whose page is taller than the window, where the
|
||||
/// wheel pans (#1924). Once the view is at the bottom (top) edge, scrolling on turns to the
|
||||
/// next (previous) page, which shows its top (bottom). A smooth gesture that panned the page
|
||||
/// has to end first (see `WheelPager::feed_edge`). `can_turn` is false when something else
|
||||
/// owns the wheel. Returns whether the page turned.
|
||||
pub fn single_page_wheel_edge(&mut self, unit: egui::MouseWheelUnit, dy: f32, phase: egui::TouchPhase, now: f64, can_turn: bool) -> bool {
|
||||
if self.continuous {
|
||||
return false;
|
||||
}
|
||||
// Only an up-to-date view counts: the page drawn is the shown one, with no jump pending.
|
||||
let edge = self.goto.is_none()
|
||||
&& self.goto_point.is_none()
|
||||
&& self.single_edges.is_some_and(|(page, top, bottom)| page == self.current && if dy < 0.0 { bottom } else { top });
|
||||
let Some(forward) = self.wheel.feed_edge(unit, dy, phase, now, can_turn && edge) else { return false };
|
||||
let before = self.current;
|
||||
self.step_page(forward);
|
||||
if self.current == before {
|
||||
return false;
|
||||
}
|
||||
if !forward {
|
||||
// Going back lands on the bottom of the previous page.
|
||||
self.goto = Some((self.current, 1.0));
|
||||
}
|
||||
self.single_edges = None;
|
||||
true
|
||||
}
|
||||
|
||||
/// Displayed page size in points for this view rotation.
|
||||
fn display_size(&self, p: &pdfcraft_render::PageInfo) -> (f32, f32) {
|
||||
if self.rotation % 180 == 90 { (p.height, p.width) } else { (p.width, p.height) }
|
||||
@@ -2231,17 +2265,19 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {
|
||||
};
|
||||
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. Touch drags are untouched: with
|
||||
// pages instead; zoomed in far enough to pan, it pans, and at the edge it turns the page on
|
||||
// (#1924, `single_page_wheel_edge`). Touch drags are untouched: with
|
||||
// nothing to pan, touch users turn pages with the rail buttons, the page box and the arrow
|
||||
// keys. Runs before `visible_pages` so the frame that turns the page draws it.
|
||||
let mut single_paging = false;
|
||||
let mut edge_turned = false;
|
||||
if !view.continuous {
|
||||
// The row's bounding box on screen (one page, or a two-page spread).
|
||||
let row_rect = row.iter().filter_map(|&i| rects.get(i)).copied().reduce(Rect::union);
|
||||
// Within a point, so layout rounding can't stop a fitting page from turning.
|
||||
let fits = row_rect.is_some_and(|r| r.height() + 2.0 * MARGIN <= avail.height() + 1.0);
|
||||
let can_turn = fits && !middle_gesture && unobstructed && ui.rect_contains_pointer(avail);
|
||||
single_paging = can_turn;
|
||||
let can_turn = !middle_gesture && unobstructed && ui.rect_contains_pointer(avail);
|
||||
single_paging = can_turn && fits;
|
||||
// Every wheel event goes to the pager, so it follows each trackpad touch to its end
|
||||
// even while the page can't turn.
|
||||
ui.input(|i| {
|
||||
@@ -2249,11 +2285,15 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {
|
||||
if let egui::Event::MouseWheel { unit, delta, phase, modifiers } = e {
|
||||
// Zooming (⌘ or Ctrl) and sideways scrolling (Shift) never turn pages.
|
||||
let dy = if modifiers.command || modifiers.ctrl || modifiers.shift { 0.0 } else { delta.y };
|
||||
view.single_page_wheel(*unit, dy, *phase, i.time, can_turn);
|
||||
if fits {
|
||||
view.single_page_wheel(*unit, dy, *phase, i.time, can_turn);
|
||||
} else {
|
||||
edge_turned |= view.single_page_wheel_edge(*unit, dy, *phase, i.time, can_turn);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if can_turn {
|
||||
if single_paging {
|
||||
// Paging owns vertical wheel motion here. A diagonal gesture keeps its sideways
|
||||
// part, as `ScrollArea` itself handles each axis.
|
||||
ui.input_mut(|i| i.smooth_scroll_delta.y = 0.0);
|
||||
@@ -2290,8 +2330,15 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {
|
||||
view.precise_scroll_until = now + 0.25;
|
||||
}
|
||||
}
|
||||
if edge_turned {
|
||||
// The turn leaves egui's eased copy of the notch to decay for a few frames; it must not
|
||||
// scroll the page just shown away from its top (or bottom).
|
||||
view.precise_scroll_until = now + 0.25;
|
||||
}
|
||||
// Paging owns the vertical axis in single-page view; leave it to the pager.
|
||||
if single_paging {
|
||||
// The rest of a gesture that turned the page is swallowed too, or it would pan the new page
|
||||
// away from the edge it was shown at.
|
||||
if single_paging || (!view.continuous && view.wheel.gesture_turned()) {
|
||||
precise_scroll.y = 0.0;
|
||||
}
|
||||
if now < view.precise_scroll_until {
|
||||
@@ -2338,6 +2385,9 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {
|
||||
});
|
||||
// Each document keeps its own scroll position when switching tabs (#189).
|
||||
scroll = scroll.id_salt(("page-view", view.id));
|
||||
// Every scroll here asks for no animation. An animated area would still queue a zero-length
|
||||
// offset target for the next frame, which overwrites the jump a page turn just set.
|
||||
scroll = scroll.animated(false);
|
||||
// A newly opened document starts at the top. eframe saves egui's memory with the settings
|
||||
// and document ids start again at 1 every launch, so the offset stored under this id can be
|
||||
// where an earlier session left another document.
|
||||
@@ -2351,6 +2401,9 @@ 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];
|
||||
|
||||
@@ -53,12 +53,16 @@ pub(crate) struct WheelPager {
|
||||
last_event: f64,
|
||||
/// egui time of the last smooth (non-notch) motion.
|
||||
last_smooth: f64,
|
||||
/// The current smooth gesture already panned the page (edge paging only): it must end
|
||||
/// before it can turn, so momentum that carries the view to the page's edge doesn't go on
|
||||
/// to turn the page.
|
||||
panned: bool,
|
||||
}
|
||||
|
||||
impl Default for WheelPager {
|
||||
fn default() -> Self {
|
||||
// Infinitely long ago, so the first event never looks like part of a stream.
|
||||
Self { accum: 0.0, turned: None, touch: None, finger_up: None, last_event: f64::NEG_INFINITY, last_smooth: f64::NEG_INFINITY }
|
||||
Self { accum: 0.0, turned: None, touch: None, finger_up: None, last_event: f64::NEG_INFINITY, last_smooth: f64::NEG_INFINITY, panned: false }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,10 +73,27 @@ impl WheelPager {
|
||||
self.accum = 0.0;
|
||||
}
|
||||
|
||||
/// Whether the current gesture already turned a page: the rest of it must not pan the page
|
||||
/// just shown away from its top (or bottom).
|
||||
pub(crate) fn gesture_turned(&self) -> bool {
|
||||
self.turned.is_some()
|
||||
}
|
||||
|
||||
/// Feed one `Event::MouseWheel`: `dy` is its vertical delta (negative scrolls forward) and
|
||||
/// `now` is egui time. With `can_turn` false (the page doesn't fit, a dialog is open…) the
|
||||
/// gesture is followed but never turns. Returns the way to turn (`true` = forward).
|
||||
pub(crate) fn feed(&mut self, unit: MouseWheelUnit, dy: f32, phase: TouchPhase, now: f64, can_turn: bool) -> Option<bool> {
|
||||
self.feed_inner(unit, dy, phase, now, can_turn, false)
|
||||
}
|
||||
|
||||
/// Like [`Self::feed`], for a page taller than the window, where `can_turn` means the view
|
||||
/// is at the edge the motion pushes against. A smooth gesture that panned the page first
|
||||
/// doesn't turn it: the user lifts and swipes again (a wheel notch still turns at once).
|
||||
pub(crate) fn feed_edge(&mut self, unit: MouseWheelUnit, dy: f32, phase: TouchPhase, now: f64, can_turn: bool) -> Option<bool> {
|
||||
self.feed_inner(unit, dy, phase, now, can_turn, true)
|
||||
}
|
||||
|
||||
fn feed_inner(&mut self, unit: MouseWheelUnit, dy: f32, phase: TouchPhase, now: f64, can_turn: bool, edge: bool) -> Option<bool> {
|
||||
if !now.is_finite() {
|
||||
return None;
|
||||
}
|
||||
@@ -96,16 +117,17 @@ impl WheelPager {
|
||||
self.touch = None;
|
||||
None
|
||||
}
|
||||
TouchPhase::Move => self.motion(unit, dy, now, idle, can_turn),
|
||||
TouchPhase::Move => self.motion(unit, dy, now, idle, can_turn, edge),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_gesture(&mut self) {
|
||||
self.accum = 0.0;
|
||||
self.turned = None;
|
||||
self.panned = false;
|
||||
}
|
||||
|
||||
fn motion(&mut self, unit: MouseWheelUnit, dy: f32, now: f64, idle: f64, can_turn: bool) -> Option<bool> {
|
||||
fn motion(&mut self, unit: MouseWheelUnit, dy: f32, now: f64, idle: f64, can_turn: bool, edge: bool) -> Option<bool> {
|
||||
if !dy.is_finite() || dy == 0.0 {
|
||||
return None;
|
||||
}
|
||||
@@ -136,6 +158,10 @@ impl WheelPager {
|
||||
if !can_turn {
|
||||
// Panning a page that doesn't fit: that motion must not count toward a turn later.
|
||||
self.accum = 0.0;
|
||||
self.panned |= edge;
|
||||
return None;
|
||||
}
|
||||
if self.panned {
|
||||
return None;
|
||||
}
|
||||
if self.turned == Some(forward) {
|
||||
@@ -280,6 +306,23 @@ mod tests {
|
||||
assert_eq!(turns(&mut p, &[(Line, -1.0, Move, 3.0)]), [true]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edge_paging_needs_a_gesture_that_did_not_pan() {
|
||||
let mut p = WheelPager::default();
|
||||
// A swipe that pans first (not at the edge yet), then reaches it: no turn, momentum included.
|
||||
let mut n = 0;
|
||||
for (unit, dy, phase, at) in swipe(1.0, -25.0) {
|
||||
n += usize::from(p.feed_edge(unit, dy, phase, at, at > 1.2).is_some());
|
||||
}
|
||||
assert_eq!(n, 0);
|
||||
// A fresh swipe that starts at the edge turns once.
|
||||
let turned: Vec<_> = swipe(3.0, -25.0).into_iter().filter_map(|(u, dy, ph, at)| p.feed_edge(u, dy, ph, at, true)).collect();
|
||||
assert_eq!(turned, [true]);
|
||||
// A wheel notch at the edge turns at once, even right after panning.
|
||||
assert_eq!(p.feed_edge(Line, -1.0, Move, 5.0, false), None);
|
||||
assert_eq!(p.feed_edge(Line, -1.0, Move, 5.3, true), Some(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hostile_input_never_turns_more_than_once() {
|
||||
let mut p = WheelPager::default();
|
||||
|
||||
@@ -198,6 +198,95 @@ fn single_page_wheel_pans_instead_when_zoomed_in() {
|
||||
assert_eq!(h.state().views[0].current, 0, "zoomed-in wheel pans, it does not turn the page");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_page_wheel_at_the_bottom_edge_turns_to_the_next_page_top_and_back() {
|
||||
// #1924: zoomed in so the page is taller than the window, the wheel pans; at the bottom
|
||||
// edge one more notch shows the next page at its top, and at the top edge one notch up
|
||||
// shows the previous page at its bottom.
|
||||
let mut h = harness_stepping(1.0 / 60.0, &[("layout", "single"), ("zoom", "200")]);
|
||||
let at = h.state().views[0].viewport_rect().center();
|
||||
h.hover_at(at);
|
||||
h.run_steps(4);
|
||||
// Notches pan page 1 down; the page turns only after the view stopped at the bottom.
|
||||
let (mut notches, mut stalled) = (0, 0);
|
||||
let mut last = rect(&h, 0).expect("page 1 on screen").min.y;
|
||||
while h.state().views[0].current == 0 && notches < 80 {
|
||||
h.event(wheel(MouseWheelUnit::Line, -1.0, TouchPhase::Move));
|
||||
h.run_steps(12);
|
||||
notches += 1;
|
||||
if let Some(r) = rect(&h, 0) {
|
||||
stalled = if (r.min.y - last).abs() < 0.01 { stalled + 1 } else { 0 };
|
||||
last = r.min.y;
|
||||
}
|
||||
}
|
||||
assert!(notches > 2, "the page panned before it turned: {notches} notches");
|
||||
assert_eq!(h.state().views[0].current, 1, "a notch at the bottom edge shows the next page");
|
||||
assert!(stalled <= 1, "no notch was wasted at the edge");
|
||||
let top = rect(&h, 1).expect("page 2 on screen").min.y;
|
||||
assert!(top > h.state().views[0].viewport_rect().min.y, "page 2 shows its top edge: {top}");
|
||||
h.event(wheel(MouseWheelUnit::Line, 1.0, TouchPhase::Move));
|
||||
h.run_steps(30);
|
||||
assert_eq!(h.state().views[0].current, 0, "a notch at the top edge shows the previous page");
|
||||
let r = rect(&h, 0).expect("page 1 on screen");
|
||||
assert!(r.max.y < h.state().views[0].viewport_rect().max.y + 40.0, "page 1 shows its bottom edge: {r:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_page_swipe_that_reaches_the_edge_does_not_turn_but_a_fresh_one_does() {
|
||||
// Momentum that carries a swipe to the bottom edge must not go on to turn the page.
|
||||
let mut h = harness_stepping(1.0 / 60.0, &[("layout", "single"), ("zoom", "200")]);
|
||||
let at = h.state().views[0].viewport_rect().center();
|
||||
h.hover_at(at);
|
||||
h.run_steps(4);
|
||||
let send = |h: &mut Harness<'static, PdfCraftApp>, phase, dy| {
|
||||
h.event(wheel(MouseWheelUnit::Point, dy, phase));
|
||||
h.step();
|
||||
};
|
||||
// One long swipe: far more motion than the page has, in one gesture.
|
||||
send(&mut h, TouchPhase::Start, 0.0);
|
||||
for _ in 0..120 {
|
||||
send(&mut h, TouchPhase::Move, -40.0);
|
||||
}
|
||||
send(&mut h, TouchPhase::End, 0.0);
|
||||
h.run_steps(30);
|
||||
assert_eq!(h.state().views[0].current, 0, "the swipe that panned to the edge does not turn");
|
||||
// A new swipe at the edge turns exactly one page.
|
||||
send(&mut h, TouchPhase::Start, 0.0);
|
||||
for _ in 0..6 {
|
||||
send(&mut h, TouchPhase::Move, -25.0);
|
||||
}
|
||||
send(&mut h, TouchPhase::End, 0.0);
|
||||
h.run_steps(30);
|
||||
assert_eq!(h.state().views[0].current, 1, "a fresh swipe at the edge turns one page");
|
||||
let top = rect(&h, 1).expect("page 2 on screen").min.y;
|
||||
assert!(
|
||||
top >= h.state().views[0].viewport_rect().min.y,
|
||||
"page 2 starts at its top after the swipe: {top} in {:?}",
|
||||
h.state().views[0].viewport_rect()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stepping_past_either_end_leaves_the_view_alone() {
|
||||
// Paging on past the first or last page must not jump the page back to its top.
|
||||
for (page, forward) in [(2, true), (0, false)] {
|
||||
let mut h = harness_stepping(1.0 / 60.0, &[("layout", "single"), ("zoom", "200")]);
|
||||
h.state_mut().views[0].go_to_page(page);
|
||||
h.run_steps(20);
|
||||
let at = h.state().views[0].viewport_rect().center();
|
||||
h.hover_at(at);
|
||||
h.run_steps(4);
|
||||
// Pan the page off its edge so a jump would show.
|
||||
h.event(wheel(MouseWheelUnit::Line, -1.0, TouchPhase::Move));
|
||||
h.run_steps(30);
|
||||
let before = rect(&h, page).expect("page on screen");
|
||||
h.state_mut().views[0].step_page(forward);
|
||||
h.run_steps(30);
|
||||
assert_eq!(h.state().views[0].current, page);
|
||||
assert_eq!(rect(&h, page), Some(before), "stepping past the end (forward={forward}) moved the page");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wheel_turns_one_page_per_notch_and_per_trackpad_swipe() {
|
||||
// At a real frame rate: brisk notches each turn a page, and one trackpad swipe turns one
|
||||
|
||||
@@ -866,8 +866,8 @@ milestone = "M3"
|
||||
status = "shipped"
|
||||
commands = ["view.layout.single"]
|
||||
tools = []
|
||||
evidence = ["crates/ui-egui/tests/view.rs::single_page_layout_shows_one_page_at_a_time", "crates/ui-egui/tests/view.rs::single_page_wheel_pans_instead_when_zoomed_in", "crates/ui-egui/tests/view.rs::wheel_turns_one_page_per_notch_and_per_trackpad_swipe", "crates/ui-egui/tests/view.rs::rail_page_display_button_switches_layouts", "crates/ui-egui/tests/view.rs::view_menu_page_display_uses_the_same_commands"]
|
||||
notes = "when the whole page fits, each wheel notch turns a page and each trackpad swipe turns one, momentum included; zoomed in, the wheel pans and never turns the page. Switched from the View menu, the rail's Page display button or the palette"
|
||||
evidence = ["crates/ui-egui/tests/view.rs::single_page_layout_shows_one_page_at_a_time", "crates/ui-egui/tests/view.rs::single_page_wheel_pans_instead_when_zoomed_in", "crates/ui-egui/tests/view.rs::wheel_turns_one_page_per_notch_and_per_trackpad_swipe", "crates/ui-egui/tests/view.rs::single_page_wheel_at_the_bottom_edge_turns_to_the_next_page_top_and_back", "crates/ui-egui/tests/view.rs::single_page_swipe_that_reaches_the_edge_does_not_turn_but_a_fresh_one_does", "crates/ui-egui/tests/view.rs::rail_page_display_button_switches_layouts", "crates/ui-egui/tests/view.rs::view_menu_page_display_uses_the_same_commands"]
|
||||
notes = "when the whole page fits, each wheel notch turns a page and each trackpad swipe turns one, momentum included; zoomed in so the page is taller than the window, the wheel pans, and at the bottom (top) edge a wheel notch, or a fresh trackpad swipe, shows the next page at its top (the previous page at its bottom); a swipe that panned to the edge, momentum included, does not turn until the fingers lift and swipe again (#1924). Not verified against Acrobat's own thresholds. Switched from the View menu, the rail's Page display button or the palette"
|
||||
|
||||
[[feature]]
|
||||
id = "view.layout-two-up"
|
||||
|
||||
Reference in New Issue
Block a user