use std::path::PathBuf;
use std::rc::Rc;
use crossbeam_channel::{Receiver, Sender};
use floem::ext_event::create_signal_from_channel;
use floem::reactive::{create_effect, provide_context, RwSignal, SignalGet, SignalUpdate};
use floem::IntoView;
use crate::error::ThemeError;
use crate::observer::FileObserver;
use crate::parser::parse_css;
use crate::style::StyleMap;
use crate::ProviderOptions;
pub struct StyleProvider {
path: PathBuf,
channel: (Sender<()>, Receiver<()>),
pub(crate) map: RwSignal<StyleMap>,
#[allow(unused)]
observer: FileObserver,
}
impl StyleProvider {
pub fn new(options: ProviderOptions) -> Result<Self, ThemeError> {
Self::try_from(options)
}
fn reload(&self) -> Result<(), ThemeError> {
let now = std::time::SystemTime::now();
let styles_str = floem_css_parser::read_styles(&self.path)?;
let s = styles_str.clone();
std::thread::spawn(move || {
floem_css_parser::analyze(&s);
});
let parsed_styles = parse_css(&styles_str);
if parsed_styles.is_empty() {
log::warn!("Styles parsed but no styles found");
}
self.map.update(|map| {
map.clear();
let _ = std::mem::replace(map, parsed_styles);
});
{
let elaps = std::time::SystemTime::now()
.duration_since(now)
.expect("Time is going backwards");
if elaps.as_millis() == 0 {
log::debug!("Styles parsed in {}μs", elaps.as_micros());
} else {
log::debug!("Styles parsed in {}ms", elaps.as_millis());
}
}
Ok(())
}
}
impl TryFrom<ProviderOptions> for StyleProvider {
type Error = ThemeError;
fn try_from(options: ProviderOptions) -> Result<Self, Self::Error> {
let channel = crossbeam_channel::unbounded();
let observer = FileObserver::new(&options.path, channel.0.clone(), options.recursive)?;
let theme = Self {
path: options.path,
observer,
channel,
map: RwSignal::new(StyleMap::new_const()),
};
Ok(theme)
}
}
pub fn theme_provider<V, F>(child: F, options: ProviderOptions) -> V
where
F: Fn() -> V,
V: IntoView + 'static,
{
let theme = StyleProvider::new(options).expect("Invalid theme path");
theme.reload().expect("Cannot load theme");
let observer_event = create_signal_from_channel(theme.channel.1.clone());
let rc_theme = Rc::new(theme);
provide_context(rc_theme.clone());
create_effect(move |_| {
if observer_event.get().is_some() {
if let Err(e) = rc_theme.reload() {
log::error!("Cannot reload theme: {e}");
}
}
});
child()
}