use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use std::any::TypeId;
use std::cell::RefCell;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::rc::Rc;
thread_local! {
static COMPONENT_POSITION_COUNTER: RefCell<u64> = const { RefCell::new(0) };
}
pub fn reset_component_position_counter() {
COMPONENT_POSITION_COUNTER.with(|counter| {
*counter.borrow_mut() = 0;
});
}
fn generate_stable_component_id<C: 'static>() -> u64 {
let type_id = TypeId::of::<C>();
let position = COMPONENT_POSITION_COUNTER.with(|counter| {
let mut c = counter.borrow_mut();
let pos = *c;
*c += 1;
pos
});
let mut hasher = DefaultHasher::new();
type_id.hash(&mut hasher);
position.hash(&mut hasher);
hasher.finish()
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ComponentArea(pub Rect);
impl ComponentArea {
pub fn area(&self) -> Rect {
self.0
}
}
pub trait Component: 'static {
fn render(&self, area: Rect, buffer: &mut Buffer);
}
#[doc(hidden)]
pub struct ComponentWrapper<C: Component> {
component: Rc<C>,
}
impl<C: Component> ComponentWrapper<C> {
pub fn new(component: C) -> Self {
Self {
component: Rc::new(component),
}
}
pub fn render_with_fiber(&self, area: Rect, buffer: &mut Buffer) {
use crate::context_stack::push_context;
use crate::fiber_tree::with_fiber_tree_mut;
let stable_id = generate_stable_component_id::<C>();
let fiber_id =
with_fiber_tree_mut(|tree| tree.get_or_create_fiber_by_component_id(stable_id))
.expect("render_with_fiber must be called within a render context");
with_fiber_tree_mut(|tree| {
tree.begin_render(fiber_id);
});
push_context(fiber_id, ComponentArea(area));
self.component.render(area, buffer);
with_fiber_tree_mut(|tree| {
tree.end_render();
});
}
}
impl<C: Component> Clone for ComponentWrapper<C> {
fn clone(&self) -> Self {
Self {
component: Rc::clone(&self.component),
}
}
}
impl<C: Component> crate::element::RenderableComponent for ComponentWrapper<C> {
fn render_with_fiber(&self, area: Rect, buffer: &mut Buffer) {
ComponentWrapper::render_with_fiber(self, area, buffer)
}
fn clone_box(&self) -> Box<dyn crate::element::RenderableComponent> {
Box::new(self.clone())
}
fn debug_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ComponentWrapper")
.field("component_type", &std::any::type_name::<C>())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
struct TestComponent;
impl Component for TestComponent {
fn render(&self, _area: Rect, _buffer: &mut Buffer) {}
}
struct AnotherComponent;
impl Component for AnotherComponent {
fn render(&self, _area: Rect, _buffer: &mut Buffer) {}
}
#[test]
fn test_position_counter_reset() {
reset_component_position_counter();
let id1 = generate_stable_component_id::<TestComponent>();
let id2 = generate_stable_component_id::<TestComponent>();
assert_ne!(id1, id2);
reset_component_position_counter();
let id1_again = generate_stable_component_id::<TestComponent>();
let id2_again = generate_stable_component_id::<TestComponent>();
assert_eq!(
id1, id1_again,
"Same type at same position should have same ID"
);
assert_eq!(
id2, id2_again,
"Same type at same position should have same ID"
);
}
#[test]
fn test_different_types_different_ids() {
reset_component_position_counter();
let id1 = generate_stable_component_id::<TestComponent>();
reset_component_position_counter();
let id2 = generate_stable_component_id::<AnotherComponent>();
assert_ne!(id1, id2);
}
#[test]
fn test_same_type_different_positions() {
reset_component_position_counter();
let id1 = generate_stable_component_id::<TestComponent>();
let id2 = generate_stable_component_id::<TestComponent>();
let id3 = generate_stable_component_id::<TestComponent>();
assert_ne!(id1, id2);
assert_ne!(id2, id3);
assert_ne!(id1, id3);
}
#[test]
fn test_simulated_render_frames() {
let mut ids_per_frame: Vec<Vec<u64>> = Vec::new();
for _ in 0..5 {
reset_component_position_counter();
let id1 = generate_stable_component_id::<TestComponent>();
let id2 = generate_stable_component_id::<AnotherComponent>();
ids_per_frame.push(vec![id1, id2]);
}
for i in 1..ids_per_frame.len() {
assert_eq!(
ids_per_frame[0], ids_per_frame[i],
"Component IDs should be stable across render frames"
);
}
}
#[test]
fn test_wrapper_clone() {
let wrapper1 = ComponentWrapper::new(TestComponent);
let wrapper2 = wrapper1.clone();
assert!(Rc::ptr_eq(&wrapper1.component, &wrapper2.component));
}
}