diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index e0ac7344..721e4646 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -326,16 +326,29 @@ fn present_fit(app: &mut DesignApp, rect: Rect) { /// Centre the spread `delta` spreads after (or before) the one in view (keeps zoom); stops at the /// first and last spread. Parent spreads while they are being edited. pub fn step_spread(app: &mut DesignApp, delta: isize) { - note_jump(app, false); + // At the first or last spread only the view moves, within the spread: no jump for Go Back. + let jump = spread_step(app, delta).is_some_and(|(cur, to)| cur != to); + if jump { + note_jump(app, false); + } centre_spread(app, delta); - note_jump(app, true); + if jump { + note_jump(app, true); + } +} + +/// The slot in view and the one `delta` spreads after (or before) it, stopping at the ends. +fn spread_step(app: &DesignApp, delta: isize) -> Option<(usize, usize)> { + let st = app.session.active()?; + let layout = CanvasLayout::new(&st.doc, st.editing_parents); + let cur = current_slot(app, &layout)?; + Some((cur, cur.saturating_add_signed(delta).min(layout.slots.len().saturating_sub(1)))) } fn centre_spread(app: &mut DesignApp, delta: isize) { + let Some((_, to)) = spread_step(app, delta) else { return }; let Some(st) = app.session.active() else { return }; let layout = CanvasLayout::new(&st.doc, st.editing_parents); - let Some(cur) = current_slot(app, &layout) else { return }; - let to = cur.saturating_add_signed(delta).min(layout.slots.len().saturating_sub(1)); let Some(b) = layout.slots.get(to).map(|s| s.bounds) else { return }; let Some(rect) = app.canvas_rect else { return }; if let Some(v) = app.view_mut() { diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 45becf63..d6bd7cfe 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -4289,6 +4289,47 @@ mod tests { assert_eq!(page(&app), 4, "Go Back returns to the page before Next Page"); } + #[test] + fn next_spread_at_the_last_spread_is_no_jump() { + let mut app = crate::DesignApp::new(designcraft_engine::Session::new(), crate::Services::default()); + app.session.execute("file.new", &json!({"pages": 7, "facingPages": true})).unwrap(); + let ctx = egui::Context::default(); + let frame = |app: &mut crate::DesignApp, events: Vec| { + let input = egui::RawInput { + screen_rect: Some(egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(1400.0, 900.0))), + events, + ..Default::default() + }; + let mut out = ctx.run_ui(input, |ui| { + app.logic(&ui.ctx().clone()); + app.ui(ui); + }); + out.textures_delta.clear(); + }; + let key = |k: egui::Key, modifiers: egui::Modifiers| { + vec![ + egui::Event::Key { key: k, physical_key: None, pressed: true, repeat: false, modifiers }, + egui::Event::Key { key: k, physical_key: None, pressed: false, repeat: false, modifiers }, + ] + }; + let page = |app: &crate::DesignApp| crate::canvas::current_page(app).unwrap(); + let cmd = egui::Modifiers { command: true, ctrl: true, ..Default::default() }; + let alt = egui::Modifiers::ALT; + frame(&mut app, vec![]); + frame(&mut app, vec![]); + // Pages 1 | 2-3 | 4-5 | 6-7: page 6 is the left page of the last spread. + app.run("view.goToPage", json!({"page": 6})).unwrap(); + frame(&mut app, vec![]); + assert_eq!(page(&app), 5); + // Next Spread has no spread to go to; centring the last one moves the view to its fold. + frame(&mut app, key(egui::Key::PageDown, alt)); + frame(&mut app, key(egui::Key::PageDown, alt)); + assert_ne!(page(&app), 5, "the view moved to the right page"); + frame(&mut app, key(egui::Key::PageUp, cmd)); + assert_eq!(page(&app), 0, "Go Back returns to the page before Go to Page"); + assert!(!menu_enabled(&app, "view.goBack")); + } + #[test] fn go_back_returns_to_the_view_a_jump_left_and_scrolling_adds_no_entry() { let mut app = crate::DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());