Skip to main content

rosace_theme/
themes.rs

1//! Platform-keyed theme bundle (D105 Phase 23 Step 2).
2//!
3//! `Themes` lets an app hand the framework a different [`ThemeData`] per
4//! platform, with a required fallback. The framework resolves the active
5//! theme ONCE at startup from the detected/overridden running platform
6//! (`rosace_core::use_platform()`) — widgets never see this bundle, only
7//! the single resolved `ThemeData`, exactly as `set_theme`/`use_theme` work
8//! today. Apps that don't use `Themes` are unaffected (`App` with a single
9//! `.theme(..)` keeps working — see `App::launch`).
10
11use std::collections::HashMap;
12
13use rosace_core::Platform;
14
15use crate::theme::ThemeData;
16
17/// A platform-keyed set of themes plus a required fallback.
18///
19/// ```rust,ignore
20/// let themes = Themes::new(light_theme())
21///     .platform(Platform::Ios, cupertino())
22///     .platform(Platform::Android, material());
23/// App::new().themes(themes).launch(MyApp);
24/// ```
25#[derive(Clone)]
26pub struct Themes {
27    fallback: ThemeData,
28    per_platform: HashMap<Platform, ThemeData>,
29}
30
31impl Themes {
32    /// Starts a bundle with the theme used for any platform that doesn't
33    /// get its own entry via [`Themes::platform`].
34    pub fn new(fallback: ThemeData) -> Self {
35        Self { fallback, per_platform: HashMap::new() }
36    }
37
38    /// Registers `theme` for `platform`. Chain multiple calls for multiple
39    /// platforms.
40    pub fn platform(mut self, platform: Platform, theme: ThemeData) -> Self {
41        self.per_platform.insert(platform, theme);
42        self
43    }
44
45    /// Resolves the theme for `platform` — the registered one, or the
46    /// fallback if none was registered for it.
47    pub fn resolve(&self, platform: Platform) -> ThemeData {
48        self.per_platform.get(&platform).cloned().unwrap_or_else(|| self.fallback.clone())
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    use crate::built_in::{cupertino, light_theme, material};
56
57    #[test]
58    fn resolve_returns_registered_theme_for_platform() {
59        let themes = Themes::new(light_theme())
60            .platform(Platform::Ios, cupertino())
61            .platform(Platform::Android, material());
62        assert_eq!(themes.resolve(Platform::Ios).app_bar.height, cupertino().app_bar.height);
63        assert_eq!(themes.resolve(Platform::Android).app_bar.height, material().app_bar.height);
64    }
65
66    #[test]
67    fn resolve_falls_back_for_unregistered_platform() {
68        let themes = Themes::new(light_theme()).platform(Platform::Ios, cupertino());
69        // macOS was never registered — must fall back, not panic or default-construct.
70        let resolved = themes.resolve(Platform::MacOs);
71        assert_eq!(resolved.app_bar.height, light_theme().app_bar.height);
72    }
73}