M3: End shows the end of the document, the last page down to its bottom (view.page-navigation) (#6435)

This commit is contained in:
mtlcl410 authored and GitHub committed 2026-11-09 08:49:35 +08:00
1 parent 94bdfea62b
commit de7ed00a1a
5 files changed
+69 -7

No files matched your search

+16 -3
View File
@@ -108,6 +108,16 @@ impl DocView {
self.current = page;
}
/// The end of the document (End): the last page, scrolled to its bottom as far as the view
/// goes, as browsers and word processors do (#6434). [`go_to_page`](Self::go_to_page) shows a
/// page's top.
pub fn go_to_end(&mut self) {
self.go_to_page(usize::MAX);
// A fraction past a page's top scrolls as far as the content allows (`goto`, clamped when
// applied), so the whole page down to its bottom edge.
self.goto = Some((self.current, 1.0));
}
/// Reveal a user-space rectangle without changing zoom, centred where the scroll bounds allow.
pub(crate) fn reveal_rect(&mut self, page: usize, rect: [f32; 4], info: &DocInfo) {
self.go_to_page(page);
@@ -407,11 +417,14 @@ pub fn shortcuts(view: &mut DocView, ctx: &egui::Context, speed: crate::scroll_s
view.find = None;
}
}
if key(Key::Home) {
// Home and End, and the keys listed with them for Go to first / last page: Shift+Ctrl
// (Shift+⌘ on macOS) with ↑ / ↓ or Page Up / Page Down (#6434).
let shift_cmd = |k| KeyboardShortcut::new(Modifiers::COMMAND | Modifiers::SHIFT, k);
if key(Key::Home) || pressed(shift_cmd(Key::ArrowUp)) || pressed(shift_cmd(Key::PageUp)) {
view.go_to_page(0);
}
if key(Key::End) {
view.go_to_page(usize::MAX);
if key(Key::End) || pressed(shift_cmd(Key::ArrowDown)) || pressed(shift_cmd(Key::PageDown)) {
view.go_to_end();
}
// Full screen: Space and Enter go forward a page and Backspace back, as in Acrobat (the
// arrows and Page Up / Page Down already turn pages where nothing scrolls, below).
+3
View File
@@ -30,6 +30,9 @@ pub(crate) const PASTE: Shortcut = Shortcut::cmd("V");
pub(crate) const SELECT_ALL: Shortcut = Shortcut::cmd("A");
pub(crate) const PAGE_PREV: Shortcut = Shortcut::cmd("←");
pub(crate) const PAGE_NEXT: Shortcut = Shortcut::cmd("→");
/// Go to first / last page beside Home and End (`canvas::navigation`).
pub(crate) const FIRST_PAGE: Shortcut = Shortcut::cmd_shift("↑");
pub(crate) const LAST_PAGE: Shortcut = Shortcut::cmd_shift("↓");
/// Whether shortcuts are written the macOS way (`⇧⌘S`) rather than `Ctrl+Shift+S`. egui knows the
/// platform from the build target, and on the web from the browser's user agent.
+6 -2
View File
@@ -1498,7 +1498,8 @@ fn dialog_shortcuts(ui: &mut egui::Ui) -> bool {
ui.label(egui::RichText::new(tl!("Keyboard shortcuts")).font(theme::semibold(18.0)));
ui.add_space(8.0);
use crate::commands::{
ACTUAL_SIZE, COPY, FIND_NEXT, FIND_PREV, FIT_WIDTH, PAGE_LEVEL, PAGE_NEXT, PAGE_PREV, ROTATE_CCW, ROTATE_CW, SELECT_ALL, ZOOM_IN, ZOOM_OUT,
ACTUAL_SIZE, COPY, FIND_NEXT, FIND_PREV, FIRST_PAGE, FIT_WIDTH, LAST_PAGE, PAGE_LEVEL, PAGE_NEXT, PAGE_PREV, ROTATE_CCW, ROTATE_CW,
SELECT_ALL, ZOOM_IN, ZOOM_OUT,
};
// Registered commands first (always in sync with the real bindings), then the
// keys the document view handles itself.
@@ -1528,7 +1529,10 @@ fn dialog_shortcuts(ui: &mut egui::Ui) -> bool {
(key(FIT_WIDTH), tl!("Fit to width").to_string()),
(pair(ZOOM_IN, ZOOM_OUT), scroll),
(pair(ROTATE_CW, ROTATE_CCW), tl!("Rotate view").to_string()),
(format!("{} / {}", crate::i18n::key_name("Home"), crate::i18n::key_name("End")), tl!("First / last page").to_string()),
(
format!("{} / {}, {}", crate::i18n::key_name("Home"), crate::i18n::key_name("End"), pair(FIRST_PAGE, LAST_PAGE)),
tl!("First / last page").to_string(),
),
(format!("← / →, {}", pair(PAGE_PREV, PAGE_NEXT)), tl!("Previous / next page").to_string()),
(alt_arrows, format!("{} / {}", tl!("Previous view"), tl!("Next view"))),
(named("V"), tl!("Select (V)").to_string()),
+42
View File
@@ -532,6 +532,48 @@ fn restart_page_labels(h: &mut Harness<'static, PdfCraftApp>) {
h.run_steps(3);
}
/// End shows the end of the document, the last page's bottom edge, when the page is taller than
/// the window; Home goes back to the first page's top (#6434).
#[test]
fn end_shows_the_bottom_of_the_last_page() {
use egui::Key;
let mut h = harness();
h.state_mut().active = Some(0);
h.state_mut().views[0].fit = pdfcraft_ui_egui::canvas::Fit::Width;
h.run_steps(4);
let vp = h.state().views[0].viewport_rect();
assert!(h.state().views[0].page_screen_rect(0).is_some_and(|r| r.height() > vp.height()), "a page is taller than the window");
h.key_press(Key::End);
h.run_steps(4);
let v = &h.state().views[0];
assert_eq!(v.current, 4, "the last page");
let last = v.page_screen_rect(4).expect("the last page is on screen");
assert!(last.bottom() <= vp.bottom() + 1.0 && last.bottom() > vp.bottom() - 40.0, "its bottom edge is at the window's: {last:?} in {vp:?}");
h.key_press(Key::Home);
h.run_steps(4);
let v = &h.state().views[0];
assert_eq!(v.current, 0, "the first page");
let first = v.page_screen_rect(0).expect("the first page is on screen");
assert!(first.top() >= vp.top() - 1.0 && first.top() < vp.top() + 40.0, "its top edge is at the window's: {first:?} in {vp:?}");
// The keys listed with them for Go to first / last page do the same.
let shift_cmd = egui::Modifiers::COMMAND | egui::Modifiers::SHIFT;
for (last, first) in [(Key::ArrowDown, Key::ArrowUp), (Key::PageDown, Key::PageUp)] {
h.key_press_modifiers(shift_cmd, last);
h.run_steps(4);
let v = &h.state().views[0];
let bottom = v.page_screen_rect(4).map(|r| r.bottom());
assert!(v.current == 4 && bottom.is_some_and(|b| (b - vp.bottom()).abs() < 40.0), "{last:?}: {bottom:?} in {vp:?}");
h.key_press_modifiers(shift_cmd, first);
h.run_steps(4);
let v = &h.state().views[0];
let top = v.page_screen_rect(0).map(|r| r.top());
assert!(v.current == 0 && top.is_some_and(|t| (t - vp.top()).abs() < 40.0), "{first:?}: {top:?} in {vp:?}");
}
}
#[test]
fn page_label_input_keeps_current_page_when_submitted_unchanged() {
let mut h = harness();
+2 -2
View File
@@ -1154,8 +1154,8 @@ milestone = "M3"
status = "shipped"
commands = []
tools = []
evidence = ["crates/ui-egui/tests/it/control.rs::state_and_view_options", "crates/ui-egui/tests/it/ui.rs::theme_and_view_options_apply", "crates/ui-egui/tests/it/viewer.rs::two_page_view_steps_a_spread_at_a_time", "crates/ui-egui/tests/it/viewer.rs::arrow_and_page_keys_move_through_a_scrolling_document", "crates/ui-egui/tests/it/viewer.rs::left_and_right_scroll_a_page_wider_than_the_window_before_turning_it"]
notes = "in two-page view, next and previous page move a whole spread (with or without a cover page); → / ← turn pages in every layout (on a page wider than the window, a plan zoomed in, they scroll sideways a line first and turn the page at its edge; ⌘→ / ⌘← turn at once), ↓ / ↑ scroll a line and Page Down / Page Up a screen (the page in single-page view), as in Acrobat; in single-page view ↓ / ↑ turn the page once there is nothing left to scroll that way, like the wheel"
evidence = ["crates/ui-egui/tests/it/control.rs::state_and_view_options", "crates/ui-egui/tests/it/ui.rs::theme_and_view_options_apply", "crates/ui-egui/tests/it/viewer.rs::two_page_view_steps_a_spread_at_a_time", "crates/ui-egui/tests/it/viewer.rs::arrow_and_page_keys_move_through_a_scrolling_document", "crates/ui-egui/tests/it/viewer.rs::left_and_right_scroll_a_page_wider_than_the_window_before_turning_it", "crates/ui-egui/tests/it/viewer.rs::end_shows_the_bottom_of_the_last_page"]
notes = "in two-page view, next and previous page move a whole spread (with or without a cover page); → / ← turn pages in every layout (on a page wider than the window, a plan zoomed in, they scroll sideways a line first and turn the page at its edge; ⌘→ / ⌘← turn at once), ↓ / ↑ scroll a line and Page Down / Page Up a screen (the page in single-page view), as in Acrobat; in single-page view ↓ / ↑ turn the page once there is nothing left to scroll that way, like the wheel; Home shows the first page's top and End the end of the document, the last page down to its bottom edge; Shift+Ctrl (Shift+⌘ on macOS) with ↑ / ↓ or Page Up / Page Down do the same as Home and End (Shift+Ctrl+Page Up / Page Down used to turn one page, like Ctrl+Page Up / Page Down)"
[[feature]]
id = "view.goto-page-label"