Keep Open and Save asynchronous to prevent macOS resize crashes (#1127)

Open and Save use parented AsyncFileDialog sheets; their answers run on the UI thread in a later frame, so resizing the window while a picker is open no longer re-enters winit and aborts. Editors, tab changes, Close/Quit and model-changing control requests wait for the answer; the local library Open and Export in the Appearance dialog use the same flow. Fixes #1088.
This commit is contained in:
Karl Von Richter authored and GitHub committed 2026-10-14 13:41:29 -04:00
1 parent f425089983
commit 37547f8eba
18 files changed
+564 -137

No files matched your search

+8 -2
View File
@@ -73,6 +73,12 @@ mod web {
if self.files.ctx.borrow().is_none() {
*self.files.ctx.borrow_mut() = Some(ctx.clone());
}
// A ready picker answer is consumed in app.logic later in this frame. Keep
// incoming files queued until then so they cannot replace its target design.
if self.app.file_picker_busy() {
ctx.request_repaint();
return;
}
files::flush(&self.files);
for (text, error) in self.files.messages.borrow_mut().drain(..) {
self.app.set_status(text, error);
@@ -434,12 +440,12 @@ mod web {
// Open shows the browser's files (and the computer's, from there).
pick_open: Some(Box::new(move || {
panel.panel.set(true);
None
Box::pin(std::future::ready(None))
})),
// Designs are kept in the browser; everything else is downloaded.
pick_save: Some(Box::new(|name: &str, exts: &[&str]| {
let design = exts.contains(&solidcraft_engine::io::DESIGN_EXT);
Some(format!("{}{name}", if design { files::OPFS } else { files::DOWNLOADS }))
Box::pin(std::future::ready(Some(format!("{}{name}", if design { files::OPFS } else { files::DOWNLOADS }))))
})),
..Default::default()
};
+17 -11
View File
@@ -50,12 +50,14 @@ impl eframe::App for App {
solidcraft_ui_egui::documents::request_quit(&mut self.solve);
}
#[cfg(target_os = "macos")]
for f in open_documents::take() {
for f in if self.solve.file_picker_busy() { Vec::new() } else { open_documents::take() } {
self.solve.open_path_async(&f);
}
self.solve.logic(ctx);
#[cfg(any(target_os = "windows", target_os = "linux"))]
self.space_mouse.tick(&mut self.solve, ctx);
if !self.solve.file_picker_busy() {
self.space_mouse.tick(&mut self.solve, ctx);
}
if self.solve.quit_requested {
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
}
@@ -81,10 +83,13 @@ impl eframe::App for App {
}
}
fn services() -> Services {
fn services(cc: &eframe::CreationContext<'_>) -> Services {
let parent = rfd::AsyncFileDialog::new().set_parent(cc);
let open_parent = parent.clone();
Services {
pick_open: Some(Box::new(|| {
rfd::FileDialog::new()
pick_open: Some(Box::new(move || {
let future = open_parent
.clone()
.add_filter(
"Designs, STEP, IGES and meshes",
&[
@@ -111,11 +116,11 @@ fn services() -> Services {
.add_filter("Mesh (3MF, STL)", &["3mf", "3MF", "stl", "STL"])
.add_filter("Sketch drawings (DXF, SVG)", &["dxf", "DXF", "svg", "SVG"])
.add_filter("All files", &["*"])
.pick_file()
.map(|p| p.to_string_lossy().to_string())
.pick_file();
Box::pin(async move { future.await.map(|p| p.path().to_string_lossy().into_owned()) })
})),
pick_save: Some(Box::new(|name: &str, exts: &[&str]| {
let dialog = rfd::FileDialog::new().set_file_name(name);
pick_save: Some(Box::new(move |name: &str, exts: &[&str]| {
let dialog = parent.clone().set_file_name(name);
let dialog = if exts == [io::DESIGN_EXT, "json"] {
if name.ends_with(".json") {
dialog.add_filter("Plain JSON design", &["json"]).add_filter("SolidCraft container", &[io::DESIGN_EXT])
@@ -125,7 +130,8 @@ fn services() -> Services {
} else {
dialog.add_filter("File", exts)
};
dialog.save_file().map(|p| p.to_string_lossy().to_string())
let future = dialog.save_file();
Box::pin(async move { future.await.map(|p| p.path().to_string_lossy().into_owned()) })
})),
graphics_path: graphics_path().map(|p| p.to_string_lossy().to_string()),
http_get: Some(std::sync::Arc::new(http_get)),
@@ -422,7 +428,7 @@ fn main() -> eframe::Result {
Box::new(move |cc| {
created_in_app.store(true, std::sync::atomic::Ordering::SeqCst);
solidcraft_ui_egui::fonts::setup_fonts(&cc.egui_ctx);
let mut app = SolveApp::new(Session::default(), services());
let mut app = SolveApp::new(Session::default(), services(cc));
app.custom_titlebar = CUSTOM_TITLEBAR;
app.integrated_titlebar = cfg!(target_os = "macos");
app.cpu_meshing = safe_mode;