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}