Layout: Go Back after Presentation returns to the last spread presented

Presentation's spreads now count as one jump in the window's history, and
leaving it as a jump back to the view it was entered from, at the place the
view was. Before, the history stayed on the last presented spread while the
view was put back, so Go Back couldn't reach it.

docs/gaps.md: Go Back and Go Forward are two of the missing menu items, 149.
This commit is contained in:
Marco Arnulfo
2026-10-23 11:26:32 +02:00
parent 86356d15be
commit 5539e28647
4 changed files with 85 additions and 4 deletions
+1 -1
View File
@@ -262,7 +262,7 @@ fn page_ids(doc: &Document) -> Vec<designcraft_doc::ids::PageId> {
/// The page in view and the view centre's offset from that page's centre. Parent pages have none,
/// nor has a view still waiting to be fitted (leaving parent pages for a document page).
fn page_in_view(app: &DesignApp) -> Option<(designcraft_doc::ids::PageId, DVec2)> {
pub(crate) fn page_in_view(app: &DesignApp) -> Option<(designcraft_doc::ids::PageId, DVec2)> {
let st = app.session.active().filter(|st| !st.editing_parents)?;
let (v, r) = (app.view().filter(|v| v.fitted)?, app.canvas_rect?);
let abs = current_page(app)?;
+8
View File
@@ -194,6 +194,12 @@ pub struct UiState {
/// The view (by its `views` key) Presentation was entered from; leaving puts it back.
#[serde(skip)]
pub presentation_view: Option<(u64, View)>,
/// The page in that view and the place on it, for Go Back after leaving Presentation.
#[serde(skip)]
pub presentation_page: Option<(designcraft_doc::ids::PageId, designcraft_geom::Vec2)>,
/// That view's Go Back / Go Forward history; leaving puts it back with one jump added.
#[serde(skip)]
pub(crate) presentation_history: Option<page_history::PageHistory>,
pub frame_edges: bool,
pub rulers: bool,
pub guides: bool,
@@ -362,6 +368,8 @@ impl Default for UiState {
screen_mode: ScreenMode::Normal,
presentation_return: ScreenMode::Normal,
presentation_view: None,
presentation_page: None,
presentation_history: None,
frame_edges: true,
rulers: true,
guides: true,
+75 -2
View File
@@ -1978,13 +1978,28 @@ fn set_screen_mode(app: &mut DesignApp, mode: ScreenMode) {
app.ui.presentation_return = app.ui.screen_mode;
// Presentation fits the spread to the screen; leaving it puts the user's view back.
app.ui.presentation_view = app.view_key().zip(app.view().copied());
app.ui.presentation_page = crate::canvas::page_in_view(app);
app.ui.presentation_history = app.page_history().cloned();
}
if mode != ScreenMode::Presentation
&& presenting
&& let Some((k, v)) = app.ui.presentation_view.take()
&& let Some(view) = app.views.get_mut(&k)
&& app.views.contains_key(&k)
{
*view = v;
// Go Back sees the presentation as one jump to the last spread presented, and putting the
// view back as a jump from there: the spreads in between would otherwise have replaced the
// place the view was at with the presentation's.
let shown = app.ui.presentation_page.take().zip(crate::canvas::page_in_view(app)).filter(|_| app.view_key() == Some(k));
let h = app.page_history.entry(k).or_default();
*h = app.ui.presentation_history.take().unwrap_or_default();
if let Some(((from, at), (to, offset))) = shown {
h.leave(from, at);
h.visit(to, offset);
h.visit(from, at);
}
if let Some(view) = app.views.get_mut(&k) {
*view = v;
}
}
app.ui.screen_mode = mode;
}
@@ -4346,6 +4361,64 @@ mod tests {
assert_eq!(spread(&app), 2);
}
#[test]
fn go_back_after_presentation_returns_to_the_last_spread_presented() {
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<egui::Event>| {
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 spread = |app: &crate::DesignApp| {
let st = app.session.active().unwrap();
crate::canvas::current_slot(app, &designcraft_tools::layout::CanvasLayout::new(&st.doc, false)).unwrap()
};
let cmd = egui::Modifiers { command: true, ctrl: true, ..Default::default() };
frame(&mut app, vec![]);
frame(&mut app, vec![]);
// Scrolled off the page's centre, so the place Go Back returns to is not just the page.
let view = app.view_mut().unwrap();
view.origin.x += 40.0;
view.origin.y += 60.0;
frame(&mut app, vec![]);
assert_eq!(spread(&app), 0);
let before = *app.view().unwrap();
// Presentation goes on to spreads 1 and 2; leaving it puts spread 0 back as it was.
app.run("view.screenMode", json!({"mode": "presentation"})).unwrap();
frame(&mut app, vec![]);
frame(&mut app, key(egui::Key::ArrowRight, egui::Modifiers::NONE));
frame(&mut app, key(egui::Key::ArrowRight, egui::Modifiers::NONE));
assert_eq!(spread(&app), 2);
app.run("view.screenMode", json!({"mode": "normal"})).unwrap();
frame(&mut app, vec![]);
assert_eq!(spread(&app), 0);
assert!(!menu_enabled(&app, "view.goForward"));
frame(&mut app, key(egui::Key::PageUp, cmd));
assert_eq!(spread(&app), 2, "Go Back shows the last spread presented");
frame(&mut app, key(egui::Key::PageUp, cmd));
assert_eq!(spread(&app), 0, "Go Back again, the view Presentation was entered from");
let v = *app.view().unwrap();
assert!((v.origin - before.origin).hypot() < 1e-6, "where it was: {:?} vs {:?}", v.origin, before.origin);
assert!(!menu_enabled(&app, "view.goBack"), "the presentation is one jump: spread 1 isn't kept");
frame(&mut app, key(egui::Key::PageDown, cmd));
assert_eq!(spread(&app), 2, "Go Forward");
}
#[test]
fn presentation_shows_one_spread_and_goes_through_them_with_keys_and_clicks() {
let mut app = crate::DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
+1 -1
View File
@@ -165,7 +165,7 @@ Kind: **F** feature · **U** UI/UX · **T** typography · **FF** file format ·
- **Estimate:** 30–50 h.
### 16. Menu items still missing (U)
- **Missing:** 151 of 482 in-scope items of InDesign 21.6's menu dump have no match (measured 2026-10-10; some exist under other labels): Type ▸ Notes
- **Missing:** 149 of 482 in-scope items of InDesign 21.6's menu dump have no match (measured 2026-10-10; some exist under other labels): Type ▸ Notes
and Track Changes submenus (the panels and commands exist), hyperlink and cross-reference
update commands, Convert Bullets/Numbering to Text, Convert Variable to Text, Object ▸ Clear
Effects / Clear All Transparency / Rotate 180° / Reverse Path,