// file: crates/ksp-app-backfill-desk/src/tw_splash.rs // version: 1 //! Tauri-window lifecycle for the Backfill Desk splash window. use tauri::Emitter; // rust-rules: trait-import use tauri::Manager; // rust-rules: trait-import /// Crate-internal splash window label. pub(crate) const WINDOW_LABEL_SPLASH: &str = "splash"; const SPLASH_EVENT_NAME: &str = "ksp-splash-order"; /// Resolves the required splash window or returns a typed error. pub(crate) fn require_splash_window(manager: &impl Manager) -> ksp_core_lib::Result { let window = manager.get_webview_window(WINDOW_LABEL_SPLASH); return match window { std::option::Option::Some(value) => std::result::Result::Ok(value), std::option::Option::None => std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_TAURI_WINDOW_MISSING, "Backfill Desk splash window is missing") .with_context("window_label", WINDOW_LABEL_SPLASH), ), }; } /// Starts the one-shot splash lifecycle after the splash frontend reports readiness. pub(crate) async fn splash_frontend_ready_service( app: tauri::AppHandle, invoking_window: tauri::WebviewWindow, state: &crate::AppState, ) -> ksp_core_lib::Result<()> { if invoking_window.label() != WINDOW_LABEL_SPLASH { return std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_SPLASH_ORIGIN_INVALID, "Splash readiness may only originate from the splash window") .with_context("window_label", invoking_window.label()), ); } if !state.begin_splash_sequence() { ksp_logging_lib::trace!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WINDOWS, window_label = WINDOW_LABEL_SPLASH, "duplicate splash frontend readiness ignored"); return std::result::Result::Ok(()); } let settings = state.splash_settings(); let lifecycle_started = std::time::Instant::now(); ksp_logging_lib::debug!( target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WINDOWS, window_label = WINDOW_LABEL_SPLASH, fade_in_ms = settings.fade_in_ms(), fade_in_source = settings.fade_in_source(), minimum_ms = settings.minimum_ms(), minimum_source = settings.minimum_source(), fade_out_ms = settings.fade_out_ms(), fade_out_source = settings.fade_out_source(), expected_backend_lifecycle_ms = settings.expected_backend_lifecycle_ms(), "splash frontend readiness accepted; starting splash to main lifecycle" ); let emitted = emit_order( &invoking_window, crate::SplashOrderDto::new("fade_in", std::option::Option::None, std::option::Option::None, std::option::Option::Some(settings.fade_in_ms())), ); if let std::result::Result::Err(error) = emitted { return std::result::Result::Err(error); } let emitted = emit_message(&invoking_window, "Initialisation de Backfill Desk...", "info"); if let std::result::Result::Err(error) = emitted { return std::result::Result::Err(error); } let emitted = emit_debug(&invoking_window, "Listener frontend prêt; fade-in piloté par Rust."); if let std::result::Result::Err(error) = emitted { return std::result::Result::Err(error); } sleep_and_log(&invoking_window, "fade-in", u64::from(settings.fade_in_ms()), lifecycle_started).await; let emitted = emit_message(&invoking_window, "Configuration KSP chargée.", "success"); if let std::result::Result::Err(error) = emitted { return std::result::Result::Err(error); } let emitted = emit_debug(&invoking_window, "Configuration et provenance des timings résolues via ksp-config-lib."); if let std::result::Result::Err(error) = emitted { return std::result::Result::Err(error); } let emitted = emit_message(&invoking_window, "Runtime Logging initialisé.", "success"); if let std::result::Result::Err(error) = emitted { return std::result::Result::Err(error); } let emitted = emit_message(&invoking_window, "Scaffold desktop prêt.", "success"); if let std::result::Result::Err(error) = emitted { return std::result::Result::Err(error); } sleep_and_log(&invoking_window, "minimum", settings.minimum_ms(), lifecycle_started).await; let emitted = emit_message(&invoking_window, "Initialisation terminée.", "success"); if let std::result::Result::Err(error) = emitted { return std::result::Result::Err(error); } let emitted = emit_order( &invoking_window, crate::SplashOrderDto::new("fade_out", std::option::Option::None, std::option::Option::None, std::option::Option::Some(settings.fade_out_ms())), ); if let std::result::Result::Err(error) = emitted { return std::result::Result::Err(error); } sleep_and_log(&invoking_window, "fade-out", u64::from(settings.fade_out_ms()), lifecycle_started).await; let show_main = crate::show_main_window(&app); if let std::result::Result::Err(error) = show_main { return std::result::Result::Err(error); } let destroyed = invoking_window.destroy(); if let std::result::Result::Err(error) = destroyed { return std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_TAURI_WINDOW_OPERATION_FAILED, "Cannot destroy Backfill Desk splash window") .with_context("window_label", WINDOW_LABEL_SPLASH) .with_source(error), ); } ksp_logging_lib::debug!( target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WINDOWS, window_label = WINDOW_LABEL_SPLASH, expected_backend_lifecycle_ms = settings.expected_backend_lifecycle_ms(), actual_backend_lifecycle_ms = lifecycle_started.elapsed().as_secs_f64() * 1000.0, "splash window destroyed after main activation" ); return std::result::Result::Ok(()); } fn emit_message(window: &tauri::WebviewWindow, message: &str, status: &str) -> ksp_core_lib::Result<()> { return emit_order( window, crate::SplashOrderDto::new("add_message", std::option::Option::Some(message), std::option::Option::Some(status), std::option::Option::None), ); } fn emit_debug(window: &tauri::WebviewWindow, message: &str) -> ksp_core_lib::Result<()> { if !cfg!(debug_assertions) { return std::result::Result::Ok(()); } return emit_order( window, crate::SplashOrderDto::new("add_debug", std::option::Option::Some(message), std::option::Option::None, std::option::Option::None), ); } async fn sleep_and_log(window: &tauri::WebviewWindow, phase: &str, duration_ms: u64, lifecycle_started: std::time::Instant) { let wait_started = std::time::Instant::now(); tokio::time::sleep(std::time::Duration::from_millis(duration_ms)).await; ksp_logging_lib::debug!( target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WINDOWS, window_label = window.label(), phase, configured_wait_ms = duration_ms, actual_wait_ms = wait_started.elapsed().as_secs_f64() * 1000.0, lifecycle_elapsed_ms = lifecycle_started.elapsed().as_secs_f64() * 1000.0, "splash lifecycle wait completed" ); return; } fn emit_order(window: &tauri::WebviewWindow, order: crate::SplashOrderDto) -> ksp_core_lib::Result<()> { let action = order.action.clone(); let emitted = window.emit(SPLASH_EVENT_NAME, order); return match emitted { std::result::Result::Ok(()) => { ksp_logging_lib::trace!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WINDOWS, window_label = WINDOW_LABEL_SPLASH, action = action.as_str(), "splash order emitted"); std::result::Result::Ok(()) }, std::result::Result::Err(error) => std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_TAURI_WINDOW_OPERATION_FAILED, "Cannot emit Backfill Desk splash order") .with_context("window_label", WINDOW_LABEL_SPLASH) .with_context("action", action) .with_source(error), ), }; }