diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 90b8c7097..4e0b8b10c 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -1668,7 +1668,6 @@ impl EffectcraftApp { self.run_script_tasks(&ctx); self.apply_prefs(&ctx); self.handle_events(&ctx); - panels::unsaved::close_or_quit(self, &ctx); let title = panels::unsaved::window_title(self); if title != self.window_title { ctx.send_viewport_cmd(egui::ViewportCommand::Title(title.clone())); @@ -1913,14 +1912,20 @@ impl eframe::App for EffectcraftApp { } self.issue_screenshots(ctx); self.collect_screenshots(ctx); + // eframe runs `logic` without `ui` while the window is minimized or hidden, and handles a + // close then too, so the close decision is made here, with the platform hook last. + if self.gpu_failure_applied == Some(true) { + self.close_after_device_loss(ctx); + // On macOS the app quits through `-[NSApplication terminate:]` instead of closing the + // window while AppKit's run loop is running (#341). + craft_quit::on_close_request(ctx); + } else { + panels::unsaved::close_or_quit(self, ctx); + } } fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) { if self.gpu_failure_applied == Some(true) { - self.close_after_device_loss(ui.ctx()); - // On macOS the app quits through `-[NSApplication terminate:]` instead of closing the - // window while AppKit's run loop is running (#341). - craft_quit::on_close_request(ui.ctx()); return; } self.init_gpu(frame); diff --git a/crates/ui-egui/src/panels/unsaved.rs b/crates/ui-egui/src/panels/unsaved.rs index e509f889e..9c8c08870 100644 --- a/crates/ui-egui/src/panels/unsaved.rs +++ b/crates/ui-egui/src/panels/unsaved.rs @@ -391,4 +391,19 @@ mod tests { let macos = cfg!(target_os = "macos"); assert_eq!(close_frame(&mut app, &ctx), (macos, macos), "Don't Save quits"); } + + /// While the window is minimized or hidden, eframe runs `App::logic` without `App::ui` and + /// still handles a close: `logic` asks about a modified project. + #[test] + fn a_close_while_only_logic_runs_asks_about_a_modified_project() { + let (mut app, ctx) = app(); + modified(&mut app); + let mut raw = egui::RawInput::default(); + raw.viewports.entry(egui::ViewportId::ROOT).or_default().events.push(egui::ViewportEvent::Close); + let mut frame = eframe::Frame::_new_kittest(); + let out = ctx.run_logic(&raw, |ctx| eframe::App::logic(&mut app, ctx, &mut frame)); + let canceled = out.viewport_commands.get(&egui::ViewportId::ROOT).is_some_and(|c| c.contains(&egui::ViewportCommand::CancelClose)); + assert!(canceled, "the close is canceled"); + assert_eq!(app.dialog, Some(Dialog::UnsavedChanges)); + } }