// file: crates/ksp-app-config-desk/src/splash.rs // version: 3 //! Common splash settings and frontend event contracts for Config Desk. use ts_rs::TS; // rust-rules: derive-import const ENV_SPLASH_MINIMUM_MS: &str = "KSP_DESK_SPLASH_MINIMUM_MS"; const ENV_SPLASH_FADE_MS: &str = "KSP_DESK_SPLASH_FADE_MS"; const DEFAULT_SPLASH_MINIMUM_MS: u64 = 1200; const DEFAULT_SPLASH_FADE_MS: u32 = 300; const MAX_SPLASH_MINIMUM_MS: u64 = 60_000; const MAX_SPLASH_FADE_MS: u32 = 10_000; /// Runtime timings used by the common desk splash lifecycle. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(crate) struct SplashSettings { minimum_ms: u64, fade_ms: u32, minimum_source: ksp_config_lib::ConfigEnvironmentSource, fade_source: ksp_config_lib::ConfigEnvironmentSource, } impl SplashSettings { /// Resolves splash timings through the Config-owned environment snapshot. pub(crate) fn load() -> ksp_core_lib::Result { let environment = ksp_config_lib::ConfigEnvironment::load(); let environment = match environment { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let minimum = environment.resolve_variable(ENV_SPLASH_MINIMUM_MS, std::option::Option::Some("1200")); let minimum = match minimum { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let fade = environment.resolve_variable(ENV_SPLASH_FADE_MS, std::option::Option::Some("300")); let fade = match fade { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let minimum_ms = parse_u64_setting(ENV_SPLASH_MINIMUM_MS, minimum.value(), MAX_SPLASH_MINIMUM_MS); let minimum_ms = match minimum_ms { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let fade_ms = parse_u32_setting(ENV_SPLASH_FADE_MS, fade.value(), MAX_SPLASH_FADE_MS); let fade_ms = match fade_ms { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; return std::result::Result::Ok(Self { minimum_ms, fade_ms, minimum_source: minimum.source(), fade_source: fade.source() }); } /// Returns safe in-memory timings used when the managed environment cannot be resolved. #[must_use] pub(crate) const fn fallback() -> Self { return Self { minimum_ms: DEFAULT_SPLASH_MINIMUM_MS, fade_ms: DEFAULT_SPLASH_FADE_MS, minimum_source: ksp_config_lib::ConfigEnvironmentSource::Fallback, fade_source: ksp_config_lib::ConfigEnvironmentSource::Fallback, }; } /// Returns the minimum visible duration after splash frontend readiness. #[must_use] pub(crate) const fn minimum_ms(self) -> u64 { return self.minimum_ms; } /// Returns the fade duration used for both fade-in and fade-out. #[must_use] pub(crate) const fn fade_ms(self) -> u32 { return self.fade_ms; } /// Returns the safe provenance code for the minimum duration. #[must_use] pub(crate) const fn minimum_source(self) -> &'static str { return environment_source_code(self.minimum_source); } /// Returns the safe provenance code for the fade duration. #[must_use] pub(crate) const fn fade_source(self) -> &'static str { return environment_source_code(self.fade_source); } /// Returns the minimum backend lifecycle duration from readiness until main activation. #[must_use] pub(crate) fn expected_backend_lifecycle_ms(self) -> u64 { return self.minimum_ms + u64::from(self.fade_ms); } } const fn environment_source_code(source: ksp_config_lib::ConfigEnvironmentSource) -> &'static str { return match source { ksp_config_lib::ConfigEnvironmentSource::Process => "process", ksp_config_lib::ConfigEnvironmentSource::DotEnv => "dotenv", ksp_config_lib::ConfigEnvironmentSource::Fallback => "fallback", }; } /// Command emitted by Rust to the splash frontend. #[derive(Clone, Debug, serde::Serialize, TS)] #[serde(rename_all = "camelCase")] #[ts(export, export_to = "../frontend/ts/bindings/ksp_app_config_desk/splash/SplashOrderDto.ts")] pub(crate) struct SplashOrderDto { /// Splash action name. pub(crate) action: String, /// Optional status text. pub(crate) message: std::option::Option, /// Optional animation duration in milliseconds. pub(crate) duration_ms: std::option::Option, } impl SplashOrderDto { #[must_use] pub(crate) fn new(action: &str, message: std::option::Option<&str>, duration_ms: std::option::Option) -> Self { return Self { action: action.to_owned(), message: message.map(std::string::ToString::to_string), duration_ms }; } } fn parse_u64_setting(variable_name: &str, value: &str, maximum: u64) -> ksp_core_lib::Result { let parsed = value.parse::(); let parsed = match parsed { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_SPLASH_SETTING_INVALID, "KSP desk splash duration is not a valid unsigned integer") .with_context("variable_name", variable_name) .with_source(error), ); }, }; if parsed > maximum { return std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_SPLASH_SETTING_INVALID, "KSP desk splash duration exceeds the allowed bound") .with_context("variable_name", variable_name) .with_context("maximum_ms", maximum.to_string()), ); } return std::result::Result::Ok(parsed); } fn parse_u32_setting(variable_name: &str, value: &str, maximum: u32) -> ksp_core_lib::Result { let parsed = value.parse::(); let parsed = match parsed { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_SPLASH_SETTING_INVALID, "KSP desk splash duration is not a valid unsigned integer") .with_context("variable_name", variable_name) .with_source(error), ); }, }; if parsed > maximum { return std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_SPLASH_SETTING_INVALID, "KSP desk splash duration exceeds the allowed bound") .with_context("variable_name", variable_name) .with_context("maximum_ms", maximum.to_string()), ); } return std::result::Result::Ok(parsed); } #[cfg(test)] #[path = "../unit_tests/splash.rs"] mod tests;