Skip to main content

reratui_core/
vnode.rs

1//! Virtual DOM node types
2
3use crate::component::Component;
4use crate::layout::{AnyWidget, LayoutWrapper};
5use ratatui::layout::{Constraint, Direction, Layout};
6use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
7use std::{
8    any::{Any, TypeId},
9    rc::Rc,
10};
11
12impl Default for Element {
13    fn default() -> Self {
14        Self::new()
15    }
16}
17
18impl<T: Component + 'static> From<T> for Element {
19    fn from(component: T) -> Self {
20        Element::component(component)
21    }
22}
23
24/// Type alias for the render function
25type RenderFn = Rc<dyn Fn(&dyn Any, Rect, &mut Buffer)>;
26
27/// Represents a virtual node in the virtual DOM tree.
28#[derive(Clone)]
29pub enum Element {
30    /// Represents a component in the virtual DOM tree.
31    Component {
32        /// The type ID of the component.
33        type_id: TypeId,
34        /// The props of the component.
35        props: Rc<dyn Any>,
36        /// The children of the component.
37        children: Vec<Element>,
38        /// The key of the component.
39        key: Option<String>,
40        /// The actual component instance.
41        component: Rc<dyn Component>,
42    },
43    /// Represents a primitive widget in the virtual DOM tree.
44    Widget {
45        /// The widget instance.
46        widget: Rc<dyn Any>,
47        /// Render function that knows how to render this specific widget
48        render_fn: RenderFn,
49        /// The key of the widget.
50        key: Option<String>,
51    },
52    /// Represents a text node in the virtual DOM tree.
53    Text(String),
54}
55
56impl Element {
57    /// Creates a new empty element (placeholder for compatibility)
58    pub fn new() -> Self {
59        Element::Text(String::new())
60    }
61
62    /// Creates a new component node.
63    pub fn component<C: Component + 'static>(component: C) -> Self {
64        Element::Component {
65            type_id: TypeId::of::<C>(),
66            props: Rc::new(()),
67            children: Vec::new(),
68            key: None,
69            component: Rc::new(component),
70        }
71    }
72
73    /// Creates a new widget node.
74    pub fn widget<W: Widget + Clone + 'static>(widget: W) -> Self {
75        let widget_box = Rc::new(widget.clone());
76        let render_fn = Rc::new(move |any: &dyn Any, area: Rect, buffer: &mut Buffer| {
77            if let Some(w) = any.downcast_ref::<W>() {
78                w.clone().render(area, buffer);
79            }
80        });
81
82        Element::Widget {
83            widget: widget_box,
84            render_fn,
85            key: None,
86        }
87    }
88
89    /// Creates a new text node.
90    pub fn text<S: Into<String>>(text: S) -> Self {
91        Element::Text(text.into())
92    }
93
94    /// Creates a fragment containing multiple elements.
95    /// This creates a container that can hold and render multiple child elements.
96    pub fn fragment(elements: Vec<Element>) -> Self {
97        if elements.is_empty() {
98            Element::text("")
99        } else if elements.len() == 1 {
100            elements.into_iter().next().unwrap()
101        } else {
102            // Create a fragment container that holds all elements
103
104            // Convert all elements to AnyWidget
105            let children: Vec<AnyWidget> = elements.into_iter().map(AnyWidget::VNode).collect();
106
107            // Create a vertical layout with equal constraints for each child
108            let constraints: Vec<Constraint> =
109                (0..children.len()).map(|_| Constraint::Min(0)).collect();
110
111            let layout = Layout::default()
112                .direction(Direction::Vertical)
113                .constraints(constraints);
114
115            let layout_wrapper = LayoutWrapper::new(layout, children);
116
117            Element::Widget {
118                widget: Rc::new(layout_wrapper),
119                render_fn: Rc::new(|widget, area, buffer| {
120                    if let Some(layout_wrapper) = widget.downcast_ref::<LayoutWrapper>() {
121                        layout_wrapper.clone().render(area, buffer);
122                    }
123                }),
124                key: None,
125            }
126        }
127    }
128
129    /// Sets the key for this node.
130    pub fn with_key<S: Into<String>>(mut self, key: S) -> Self {
131        match &mut self {
132            Element::Component { key: k, .. } => *k = Some(key.into()),
133            Element::Widget { key: k, .. } => *k = Some(key.into()),
134            Element::Text(_) => {} // Text nodes don't have keys
135        }
136        self
137    }
138
139    /// Renders this node to the buffer.
140    pub fn render(&self, area: Rect, buffer: &mut Buffer) {
141        match self {
142            Element::Component { component, .. } => {
143                // Render with lifecycle hooks (on_mount/on_unmount)
144                crate::component::render_component_with_lifecycle(component, area, buffer);
145            }
146            Element::Widget {
147                widget, render_fn, ..
148            } => {
149                render_fn(widget.as_ref(), area, buffer);
150            }
151            Element::Text(_) => {
152                // Text nodes are usually rendered as part of a widget
153            }
154        }
155    }
156}
157
158/// Represents a property value in the virtual DOM tree.
159#[derive(Clone)]
160pub enum PropValue {
161    /// Represents a string property value.
162    String(String),
163    /// Represents an integer property value.
164    Int(i64),
165    /// Represents a number property value.
166    Number(f64),
167    /// Represents a boolean property value.
168    Bool(bool),
169    /// Represents an object property value.
170    Object(Rc<dyn Any>),
171}