Fix #341: answered file dialogs run before the close decision

The close decision moved to App::logic, which runs before App::ui, while
the queued file-dialog answers still ran in ui. An answer that arrived in
the frame that closed the window changed the project after the decision,
so the window could close without asking to save. The answers now run in
logic, before the decision.
This commit is contained in:
dotCooCoo committed 2026-10-24 08:38:14 -07:00
1 parent eff747897d
commit ce0ae4e387
2 files changed
+20 -1

No files matched your search

+2 -1
View File
@@ -1623,7 +1623,6 @@ impl EffectcraftApp {
fn frame(&mut self, ui: &mut egui::Ui) {
let ctx = ui.ctx().clone();
self.apply_gpu_failure(&ctx);
self.run_dialog_answers();
self.auto.begin_frame();
self.frames.set_context(&ctx);
if self.session.render_job.is_some() {
@@ -1920,6 +1919,8 @@ impl eframe::App for EffectcraftApp {
// window while AppKit's run loop is running (#341).
craft_quit::on_close_request(ctx);
} else {
// An answered file dialog can change the project, so it runs before the close decision.
self.run_dialog_answers();
panels::unsaved::close_or_quit(self, ctx);
}
}
+18
View File
@@ -406,4 +406,22 @@ mod tests {
assert!(canceled, "the close is canceled");
assert_eq!(app.dialog, Some(Dialog::UnsavedChanges));
}
/// A file dialog answered in the frame that closes the window runs its command before the
/// close decision, so a change it makes is asked about.
#[test]
fn a_dialog_answer_runs_before_the_close_decision() {
let (mut app, ctx) = app();
assert!(!app.session.is_dirty());
let answer = crate::DialogAnswer { command: "comp.new".into(), key: "name", params: json!({"width": 64, "height": 64}), paths: vec!["Main".into()] };
app.dialog_answers.0.send(answer).unwrap();
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!(app.session.is_dirty(), "the answer ran");
assert!(canceled, "the close is canceled");
assert_eq!(app.dialog, Some(Dialog::UnsavedChanges));
}
}