// file: crates/ksp-app-config-desk/src/splash.rs // version: 6 //! Common splash settings and frontend event contracts for Config Desk. use ts_rs::TS; // rust-rules: trait-import const DEFAULT_SPLASH_FADE_IN_MS: u32 = 300; const DEFAULT_SPLASH_FADE_OUT_MS: u32 = 300; const DEFAULT_SPLASH_MINIMUM_MS: u64 = 1200; const ENV_SPLASH_FADE_IN_MS: &str = "KSP_DESK_SPLASH_FADE_IN_MS"; const ENV_SPLASH_FADE_OUT_MS: &str = "KSP_DESK_SPLASH_FADE_OUT_MS"; const ENV_SPLASH_MINIMUM_MS: &str = "KSP_DESK_SPLASH_MINIMUM_MS"; const MAX_SPLASH_FADE_MS: u32 = 10_000; const MAX_SPLASH_MINIMUM_MS: u64 = 60_000; /// Runtime timings used by the common desk splash lifecycle. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(crate) struct SplashSettings { fade_in_ms: u32, minimum_ms: u64, fade_out_ms: u32, fade_in_source: ksp_config_lib::ConfigEnvironmentSource, minimum_source: ksp_config_lib::ConfigEnvironmentSource, fade_out_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 fade_in = environment.resolve_variable(ENV_SPLASH_FADE_IN_MS, std::option::Option::Some("300")); let fade_in = match fade_in { 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_out = environment.resolve_variable(ENV_SPLASH_FADE_OUT_MS, std::option::Option::Some("300")); let fade_out = match fade_out { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let fade_in_ms = parse_u32_setting(ENV_SPLASH_FADE_IN_MS, fade_in.value(), MAX_SPLASH_FADE_MS); let fade_in_ms = match fade_in_ms { 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_out_ms = parse_u32_setting(ENV_SPLASH_FADE_OUT_MS, fade_out.value(), MAX_SPLASH_FADE_MS); let fade_out_ms = match fade_out_ms { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; return std::result::Result::Ok(Self { fade_in_ms, minimum_ms, fade_out_ms, fade_in_source: fade_in.source(), minimum_source: minimum.source(), fade_out_source: fade_out.source(), }); } /// Returns safe in-memory timings used when the managed environment cannot be resolved. #[must_use] pub(crate) const fn fallback() -> Self { return Self { fade_in_ms: DEFAULT_SPLASH_FADE_IN_MS, minimum_ms: DEFAULT_SPLASH_MINIMUM_MS, fade_out_ms: DEFAULT_SPLASH_FADE_OUT_MS, fade_in_source: ksp_config_lib::ConfigEnvironmentSource::Fallback, minimum_source: ksp_config_lib::ConfigEnvironmentSource::Fallback, fade_out_source: ksp_config_lib::ConfigEnvironmentSource::Fallback, }; } /// Returns the configured fade-in duration. #[must_use] pub(crate) const fn fade_in_ms(self) -> u32 { return self.fade_in_ms; } /// Returns the minimum fully-visible splash duration. #[must_use] pub(crate) const fn minimum_ms(self) -> u64 { return self.minimum_ms; } /// Returns the configured fade-out duration. #[must_use] pub(crate) const fn fade_out_ms(self) -> u32 { return self.fade_out_ms; } /// Returns the safe provenance code for the fade-in duration. #[must_use] pub(crate) const fn fade_in_source(self) -> &'static str { return environment_source_code(self.fade_in_source); } /// 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-out duration. #[must_use] pub(crate) const fn fade_out_source(self) -> &'static str { return environment_source_code(self.fade_out_source); } /// Returns the expected additive backend lifecycle from readiness through fade-out. #[must_use] pub(crate) fn expected_backend_lifecycle_ms(self) -> u64 { return u64::from(self.fade_in_ms) + self.minimum_ms + u64::from(self.fade_out_ms); } } /// 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 message severity used by the bottom message feed. pub(crate) status: std::option::Option, /// Optional animation duration in milliseconds. pub(crate) duration_ms: std::option::Option, } impl SplashOrderDto { /// Creates a new `SplashOrderDto` value. #[must_use] pub(crate) fn new(action: &str, message: std::option::Option<&str>, status: std::option::Option<&str>, duration_ms: std::option::Option) -> Self { return Self { action: action.to_owned(), message: message.map(std::string::ToString::to_string), status: status.map(std::string::ToString::to_string), duration_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", }; } 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;