use ratatui::Frame;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use std::collections::HashMap;
thread_local! {
pub(crate) static MOUNT_STATE: std::cell::RefCell<MountState> = Default::default();
}
type CleanupFn = Box<dyn Fn() + 'static>;
#[derive(Default)]
pub(crate) struct MountState {
mounted: std::collections::HashSet<usize>,
current_render: std::collections::HashSet<usize>,
cleanup_fns: HashMap<usize, CleanupFn>,
}
impl MountState {
pub(crate) fn track_mount<F>(&mut self, id_hash: usize, cleanup_fn: F) -> bool
where
F: Fn() + 'static,
{
self.current_render.insert(id_hash);
let is_new = self.mounted.insert(id_hash);
if is_new {
self.cleanup_fns.insert(id_hash, Box::new(cleanup_fn));
}
is_new
}
fn cleanup_unmounted(&mut self) {
let unmounted: Vec<_> = self
.mounted
.difference(&self.current_render)
.cloned()
.collect();
for &id_hash in &unmounted {
if let Some(cleanup_fn) = self.cleanup_fns.remove(&id_hash) {
cleanup_fn(); }
self.mounted.remove(&id_hash);
}
self.current_render.clear();
}
}
pub trait Component: 'static {
fn on_mount(&self) {}
fn on_unmount(&self) {}
fn render(&self, area: Rect, buffer: &mut Buffer);
fn component_id(&self) -> String {
std::any::type_name::<Self>().to_string()
}
fn clone_box(&self) -> Box<dyn Component>
where
Self: Clone,
{
Box::new(self.clone())
}
fn render_with_mount(&self, area: Rect, frame: &mut Frame)
where
Self: Clone,
{
let self_clone = self.clone();
let cleanup_fn = move || {
self_clone.on_unmount();
};
track_and_call_lifecycle(self, cleanup_fn);
self.render(area, frame.buffer_mut());
}
}
fn track_and_call_lifecycle<F>(component: &dyn Component, cleanup_fn: F)
where
F: Fn() + 'static,
{
let component_id = component.component_id();
let id_hash = {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
component_id.hash(&mut hasher);
hasher.finish() as usize
};
let is_first_render = MOUNT_STATE.with(|state| {
let mut state = state.borrow_mut();
state.track_mount(id_hash, cleanup_fn)
});
if is_first_render {
component.on_mount();
}
}
pub(crate) fn render_component_with_lifecycle(
component: &std::rc::Rc<dyn Component>,
area: Rect,
buffer: &mut Buffer,
) {
let component_clone = std::rc::Rc::clone(component);
let cleanup_fn = move || {
component_clone.on_unmount();
};
track_and_call_lifecycle(component.as_ref(), cleanup_fn);
component.render(area, buffer);
}
pub fn cleanup_unmounted() {
MOUNT_STATE.with(|state| {
let mut state = state.borrow_mut();
state.cleanup_unmounted();
});
}