diff --git a/Cargo.lock b/Cargo.lock index acd433bc27..57fe46d54c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1246,7 +1246,7 @@ dependencies = [ [[package]] name = "craft-update" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=a231e517c3b371e1bf0ee8af71ed807424a9a05d#a231e517c3b371e1bf0ee8af71ed807424a9a05d" +source = "git+https://github.com/dotCooCoo/craft-libs?rev=c365d75c48b0e47f4b43cdb6592d0cdc39d2d52e#c365d75c48b0e47f4b43cdb6592d0cdc39d2d52e" dependencies = [ "chrono", "dunce", diff --git a/Cargo.toml b/Cargo.toml index 8549d6167e..52f1ae9793 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-l # Canon CRW decoder shared with the other Craft apps craft-crw = { git = "https://github.com/storytold/craft-libs", rev = "f3b62ec61f45534392376492fc5ad33047375205" } # craft-update: app-specific pretend versions apply to startup and manual checks. -craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "a231e517c3b371e1bf0ee8af71ed807424a9a05d", features = ["egui"] } +craft-update = { git = "https://github.com/dotCooCoo/craft-libs", version = "=0.1.0", rev = "c365d75c48b0e47f4b43cdb6592d0cdc39d2d52e", features = ["egui"] } craft-window = { git = "https://github.com/storytold/craft-libs", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7", features = ["egui"] } # internal lightcraft-denoise-core = { path = "crates/denoise-core" } diff --git a/apps/lightcraft/src/main.rs b/apps/lightcraft/src/main.rs index 3a7bda6696..97479ad68a 100644 --- a/apps/lightcraft/src/main.rs +++ b/apps/lightcraft/src/main.rs @@ -201,6 +201,10 @@ impl eframe::App for App { // app after this and the session's last writes (face data) still go out under the lock. #[cfg(target_os = "macos")] self.0.session.release_library_lock(); + // macOS only: the process ends below without dropping the app, so a ready update's helper + // starts here. Elsewhere dropping the app starts it. + #[cfg(target_os = "macos")] + self.0.start_pending_update(); // macOS (#1331): end the process here, before eframe tears the window down, as Quit from // the menu bar already does (PR #444). AccessKit's macOS adapter subclasses the window's // content view and restores its class on teardown; AppKit's Touch Bar support observes diff --git a/crates/ui-egui/src/dialogs/updates_ui.rs b/crates/ui-egui/src/dialogs/updates_ui.rs index 27f7d0c13b..ee42c37681 100644 --- a/crates/ui-egui/src/dialogs/updates_ui.rs +++ b/crates/ui-egui/src/dialogs/updates_ui.rs @@ -21,6 +21,7 @@ pub(crate) struct Runtime { #[cfg(not(target_arch = "wasm32"))] repaint: Option, /// A requested check's result dialog is showing. + #[cfg(target_arch = "wasm32")] pub(crate) manual_window_open: bool, #[cfg(target_arch = "wasm32")] pub(crate) status_message: Option, @@ -37,6 +38,7 @@ impl Default for Runtime { check_requested: false, #[cfg(not(target_arch = "wasm32"))] repaint: None, + #[cfg(target_arch = "wasm32")] manual_window_open: false, #[cfg(target_arch = "wasm32")] status_message: None, @@ -50,6 +52,13 @@ impl LightcraftApp { pub fn set_startup_update_check(&mut self, on: bool) { self.updates_rt.startup_check = on; } + + /// Starts the helper of a ready update, which installs it once the process has exited. Call it + /// in `on_exit` when the process ends there without dropping the app. + #[cfg(not(target_arch = "wasm32"))] + pub fn start_pending_update(&mut self) { + self.updates_rt.updater.start_pending_update(); + } } /// The update prompt owns the keyboard while it is open: shortcuts must not reach the library behind it. @@ -89,7 +98,6 @@ pub(crate) fn tick(app: &mut LightcraftApp, ctx: &egui::Context) { rt.repaint = Some(ctx.clone()); if std::mem::take(&mut rt.check_requested) { rt.updater.check_now(ctx); - rt.manual_window_open = true; } else if rt.startup_check { rt.updater.check_at_start(ctx); } @@ -99,10 +107,18 @@ pub(crate) fn tick(app: &mut LightcraftApp, ctx: &egui::Context) { let _ = (app, ctx); } +/// Whether the result dialog shows: for every status but idle (a requested check, and the first +/// start after an update, which reports that it installed or why it failed), and never over the +/// startup prompt. +#[cfg(not(target_arch = "wasm32"))] +fn result_window_shows(status: &Status, prompt_open: bool) -> bool { + !prompt_open && !matches!(status, Status::Idle) +} + pub(crate) fn show(app: &mut LightcraftApp, ctx: &egui::Context) { #[cfg(not(target_arch = "wasm32"))] { - // The restart helper is scheduled before the app closes, so no update starts over unsaved work. + // The close for a ready update waits until nothing is unsaved. let clean = app.session.unsaved().is_none(); match app.updates_rt.updater.show_if(ctx, clean) { Action::OpenUrl(url) => ctx.open_url(egui::OpenUrl::new_tab(url)), @@ -111,20 +127,16 @@ pub(crate) fn show(app: &mut LightcraftApp, ctx: &egui::Context) { } // A found update opens the startup prompt and leaves the status idle, which ends the result dialog. let rt = &mut app.updates_rt; - if rt.manual_window_open { + if result_window_shows(rt.updater.status(), rt.updater.prompt_open()) { let dialog = CheckResultDialog::new(&crate::UPDATE_APP, env!("CARGO_PKG_VERSION")); match dialog.show(ctx, rt.updater.status()) { CheckResultResponse::None => {} - CheckResultResponse::Close => rt.manual_window_open = false, + // Closing the dialog dismisses the outcome it showed. + CheckResultResponse::Close => rt.updater.dismiss_status(), CheckResultResponse::CheckAgain => rt.check_requested = true, + // The next `show_if` asks the window to close once nothing is unsaved. + CheckResultResponse::RestartToUpdate => rt.updater.restart_to_update(), } - if matches!(rt.updater.status(), Status::Idle) { - rt.manual_window_open = false; - } - } - // Closing the dialog dismisses the outcome it showed. - if !rt.manual_window_open { - rt.updater.dismiss_status(); } } @@ -163,6 +175,7 @@ mod tests { #[test] fn default_runtime_is_idle() { let rt = Runtime::default(); + #[cfg(target_arch = "wasm32")] assert!(!rt.manual_window_open); #[cfg(not(target_arch = "wasm32"))] { @@ -192,4 +205,24 @@ mod tests { output.textures_delta.clear(); assert!(app.updates_rt.updater.prompt_open()); } + + #[test] + #[cfg(not(target_arch = "wasm32"))] + fn result_window_shows_every_status_but_idle() { + let shown = [ + Status::Checking, + Status::UpToDate, + Status::Failed("no network".into()), + Status::Installing, + Status::Ready, + Status::Managed, + Status::Updated("0.2.0".into()), + Status::InstallFailed("disk full".into()), + ]; + for status in &shown { + assert!(result_window_shows(status, false), "{status:?}"); + assert!(!result_window_shows(status, true), "{status:?} over the prompt"); + } + assert!(!result_window_shows(&Status::Idle, false)); + } }