diff --git a/crates/oa-desktop/src/app.rs b/crates/oa-desktop/src/app.rs index fb151417..fa71a16e 100644 --- a/crates/oa-desktop/src/app.rs +++ b/crates/oa-desktop/src/app.rs @@ -1468,8 +1468,11 @@ impl App { Message::WindowResized(size) => self.window_size = size, Message::CanvasResized(size) => { let first_measurement = self.canvas_size.is_none(); + let size_changed = self.canvas_size != Some(size); self.canvas_size = Some(size); - if first_measurement || self.fit_to_view { + // Modal overlays can remount the sensor and repeat on_show at + // the same size. Keep the viewport stable after model edits. + if first_measurement || (size_changed && self.fit_to_view) { self.fit(); } } diff --git a/crates/oa-desktop/src/tests.rs b/crates/oa-desktop/src/tests.rs index 0182379f..d0b5ea40 100644 --- a/crates/oa-desktop/src/tests.rs +++ b/crates/oa-desktop/src/tests.rs @@ -171,6 +171,57 @@ fn fit_adapts_to_canvas_returning_after_help_but_preserves_manual_navigation() { assert_eq!((app.zoom, app.pan), manual); } +#[test] +fn reopening_canvas_for_a_modal_preserves_viewport_after_route_edits() { + let mut p = Project::empty(); + let sender = p.add(Kind::Activity, "Report", 0., 0.); + let target = p.add(Kind::Activity, "Receive", 600., 0.); + let edge = p.relate(RelationKind::Interaction, "Report signal", sender, target); + let diagram = p.ensure_diagram(Layer::Oa); + let mut app = App::with_project(p); + let canvas = iced::Size::new(900., 600.); + send(&mut app, Message::CanvasResized(canvas)); + let viewport = (app.zoom, app.pan); + send( + &mut app, + Message::Command(Command::SetEdgeRoute { + diagram, + route: EdgeRoute { + edge, + waypoints: vec![Position { x: 400., y: 400. }], + label: Some(EdgeLabel { + fraction: 0.5, + offset: Position { x: 0., y: 150. }, + }), + }, + }), + ); + assert_eq!((app.zoom, app.pan), viewport); + let mut fitted = App::with_project(app.project().clone()); + send(&mut fitted, Message::CanvasResized(canvas)); + assert_ne!( + (fitted.zoom, fitted.pan), + viewport, + "The edited route must change the fitted bounds to expose an accidental refit" + ); + + // The shared file modal remounts the canvas sensor. Its on_show measurement + // describes the existing viewport, not a user request to fit the edited model. + send(&mut app, Message::Export); + assert!(app.file_prompt.is_some()); + send(&mut app, Message::CanvasResized(canvas)); + assert_eq!((app.zoom, app.pan), viewport); + send(&mut app, Message::CancelFile); + send(&mut app, Message::CanvasResized(canvas)); + assert_eq!((app.zoom, app.pan), viewport); + + let compact = iced::Size::new(650., 400.); + send(&mut app, Message::CanvasResized(compact)); + send(&mut fitted, Message::CanvasResized(compact)); + assert_eq!((app.zoom, app.pan), (fitted.zoom, fitted.pan)); + assert_ne!((app.zoom, app.pan), viewport); +} + #[test] fn source_navigation_waits_for_editor_layout_and_cancels_on_edit() { let p = fixture();