1use std::path::PathBuf;
2use std::rc::Rc;
3
4use crossbeam_channel::{Receiver, Sender};
5use floem::ext_event::create_signal_from_channel;
6use floem::reactive::{create_effect, provide_context, RwSignal, SignalGet, SignalUpdate};
7use floem::IntoView;
8
9use crate::error::ThemeError;
10use crate::observer::FileObserver;
11use crate::parser::parse_css;
12use crate::style::StyleMap;
13use crate::ProviderOptions;
14
15pub struct StyleProvider {
16 path: PathBuf,
17 channel: (Sender<()>, Receiver<()>),
18 pub(crate) map: RwSignal<StyleMap>,
19 #[allow(unused)]
20 observer: FileObserver,
21}
22
23impl StyleProvider {
24 pub fn new(options: ProviderOptions) -> Result<Self, ThemeError> {
28 Self::try_from(options)
29 }
30
31 fn reload(&self) -> Result<(), ThemeError> {
37 let now = std::time::SystemTime::now();
38 let styles_str = floem_css_parser::read_styles(&self.path)?;
39 let s = styles_str.clone();
40 std::thread::spawn(move || {
41 floem_css_parser::analyze(&s);
42 });
43 let parsed_styles = parse_css(&styles_str);
44 if parsed_styles.is_empty() {
45 log::warn!("Styles parsed but no styles found");
46 }
47 self.map.update(|map| {
48 map.clear();
49 let _ = std::mem::replace(map, parsed_styles);
50 });
51 {
52 let elaps = std::time::SystemTime::now()
53 .duration_since(now)
54 .expect("Time is going backwards");
55 if elaps.as_millis() == 0 {
56 log::debug!("Styles parsed in {}μs", elaps.as_micros());
57 } else {
58 log::debug!("Styles parsed in {}ms", elaps.as_millis());
59 }
60 }
61 Ok(())
62 }
63}
64
65impl TryFrom<ProviderOptions> for StyleProvider {
66 type Error = ThemeError;
67 fn try_from(options: ProviderOptions) -> Result<Self, Self::Error> {
68 let channel = crossbeam_channel::unbounded();
69 let observer = FileObserver::new(&options.path, channel.0.clone(), options.recursive)?;
70 let theme = Self {
71 path: options.path,
72 observer,
73 channel,
74 map: RwSignal::new(StyleMap::new_const()),
75 };
76 Ok(theme)
77 }
78}
79
80pub fn theme_provider<V, F>(child: F, options: ProviderOptions) -> V
125where
126 F: Fn() -> V,
127 V: IntoView + 'static,
128{
129 let theme = StyleProvider::new(options).expect("Invalid theme path");
130 theme.reload().expect("Cannot load theme");
131 let observer_event = create_signal_from_channel(theme.channel.1.clone());
132 let rc_theme = Rc::new(theme);
133 provide_context(rc_theme.clone());
134 create_effect(move |_| {
135 if observer_event.get().is_some() {
136 if let Err(e) = rc_theme.reload() {
137 log::error!("Cannot reload theme: {e}");
138 }
139 }
140 });
141 child()
142}