Zoom after One Page keeps one page per row (#768) (#832)

Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
MarekSpodBiedry 2026-10-12 15:05:40 +02:00 committed by GitHub
parent 7fbcd0643a
commit bc60cbaadb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 65 additions and 3 deletions

View File

@ -651,6 +651,7 @@ pub fn specs() -> Vec<CommandSpec> {
.pure(),
CommandSpec::new("view.sideToSide", "Side to Side", "View › Page Movement", |s, _| {
s.view.multi_page = true;
s.view.single_column = false;
s.view.fit = "multiplePages".into();
Ok(json!({"pageMovement": "sideToSide"}))
})

View File

@ -164,6 +164,7 @@ fn zoom(s: &mut Session, v: &Value) -> CmdResult {
Some(Value::String(f)) if ["pageWidth", "onePage", "multiplePages", "textWidth"].contains(&f.as_str()) => {
s.view.fit = f.clone();
s.view.multi_page = f == "multiplePages";
s.view.single_column = f == "onePage" || f == "pageWidth";
}
Some(x) => {
let pct = x.as_f64().ok_or_else(|| CmdError::Params("zoom percent or fit mode".into()))? as f32;

View File

@ -87,6 +87,9 @@ pub struct ViewState {
#[serde(default)]
pub alt_text_pane: bool,
pub multi_page: bool,
/// One Page or Page Width was chosen last: keep one page per row when a typed or wheel zoom replaces the fit mode.
#[serde(default)]
pub single_column: bool,
/// Zoom to fit: "pageWidth", "onePage", "multiplePages", or empty.
pub fit: String,
pub web_width: f32,
@ -169,6 +172,7 @@ impl Default for ViewState {
accessibility_pane: false,
alt_text_pane: false,
multi_page: false,
single_column: false,
fit: String::new(),
web_width: 800.0,
read_width: default_read_width(),

View File

@ -2761,6 +2761,25 @@ fn page_and_table_gridlines_toggle_independently() {
assert_eq!(state["tableGridlines"], false);
}
/// Issue #768: zooming by percent after One Page or Page Width keeps one page per row.
#[test]
fn zoom_after_one_page_keeps_single_column() {
let mut s = s();
run(&mut s, "view.onePage", json!({}));
assert!(s.view.single_column);
run(&mut s, "view.zoomOut", json!({}));
assert!(s.view.fit.is_empty() && s.view.single_column);
run(&mut s, "view.zoom", json!({"value": 50}));
assert!(s.view.single_column);
run(&mut s, "view.multiplePages", json!({}));
assert!(!s.view.single_column);
run(&mut s, "view.zoomIn", json!({}));
assert!(!s.view.single_column);
run(&mut s, "view.pageWidth", json!({}));
run(&mut s, "view.sideToSide", json!({}));
assert!(!s.view.single_column);
}
#[test]
fn navigation_pane_opens_without_changing_view_mode() {
// Reading Mode's heading navigation is the Navigation pane; Outline is an editing view (#493).

View File

@ -478,8 +478,8 @@ pub fn geometry(app: &WordApp, l: &DocLayout, avail: egui::Vec2) -> Geometry {
scale = PX_PER_PT;
}
// Pages flow side by side in Print Layout whenever more than one fits across; One Page and
// Page Width show one per row, Web Layout has no pages. Reading Mode is handled above.
let (min_cols, auto) = if web || v.fit == "onePage" || v.fit == "pageWidth" {
// Page Width show one per row (and so does a zoom after One Page, #768), Web Layout has no pages. Reading Mode is handled above.
let (min_cols, auto) = if web || v.fit == "onePage" || v.fit == "pageWidth" || v.single_column {
(1, false)
} else if v.multi_page {
(2, true)

View File

@ -2411,6 +2411,43 @@ mod tests {
assert_eq!(caret.page, 3);
}
/// Issue #768: after One Page, zooming out (Zoom Out, the Zoom box, Ctrl + wheel) keeps one page per row.
#[test]
fn zoom_after_one_page_keeps_one_page_per_row() {
let ctx = egui::Context::default();
let mut a = app();
for _ in 0..3 {
a.run("insert.pageBreak", json!({})).unwrap();
}
let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(1280.0, 800.0));
let mut t = 0.0;
let mut frame = |a: &mut WordApp| {
t += 1.0 / 60.0;
let input = egui::RawInput { time: Some(t), screen_rect: Some(screen), ..Default::default() };
ctx.run_ui(input, |ui| {
a.logic(ui.ctx());
a.ui(ui);
})
.drop_without_applying_deltas();
};
a.run("view.onePage", json!({})).unwrap();
for _ in 0..3 {
frame(&mut a);
}
assert_eq!(a.canvas.cols, 1);
a.run("view.zoom", json!({"value": 30})).unwrap();
for _ in 0..3 {
frame(&mut a);
}
assert_eq!(a.canvas.cols, 1, "30% after One Page: still one page per row");
a.run("view.multiplePages", json!({})).unwrap();
a.run("view.zoom", json!({"value": 30})).unwrap();
for _ in 0..3 {
frame(&mut a);
}
assert_eq!(a.canvas.cols, 4, "30% after Multiple Pages: all four pages fit across");
}
#[test]
fn user_name_survives_restart() {
let mut first = app();

View File

@ -27,7 +27,7 @@ own (`AGENTS.md`: no Microsoft iconography, ever).
| Tables on the page: drag column and row borders, resize handle past the bottom-right corner | ✅ resize cursors, dashed guide, one undo step per drag, selection kept; Shift moves one column; Esc cancels (#217). Missing: Ctrl and Alt drags, the left edge, borders of selected cells only, double-click AutoFit | ✅ | 80% | 1–2 |
| Rotation handle, crop handles on canvas, alignment guides, smart guides | rotation handle (Shift: 15° steps), turned frames resize along their axes (#332); crop handles and guides missing | ✅ | 25% | 3–5 |
| Selection: word/sentence/paragraph clicks, dragging after a double-click/triple-click/Ctrl+click extends by whole words/paragraphs/sentences (#549), Shift extend, F8 extend mode, column (Alt+drag, #237) | ✅ | ✅ | 90% | 1 |
| Zoom: Ctrl+scroll, pinch (#179), zoom buttons (#232), pages side by side when zoomed out (#251) | ✅ | ✅ | 85% | — |
| Zoom: Ctrl+scroll, pinch (#179), zoom buttons (#232), pages side by side when zoomed out (#251), still one per row after One Page or Page Width (#768) | ✅ | ✅ | 85% | — |
| Touchpad smooth/inertial scroll | ✅ (#252) | ✅ | 90% | — |
| Reading Mode | Existing Read Mode ribbon button opens a read-only, viewport-column layout with animated horizontal paging, transient width/text/spacing controls, heading navigation (Navigation pane) and resize anchoring; print/export layout unchanged | reading-optimized view and navigation | 75% | 1–2 |
| Status bar (page x of y, words, language, view buttons, zoom slider) | ✅ (the language is the caret's; a click sets the proofing language) | ✅ | 85% | — |