Skip to main content

euv_core/vdom/node/
impl.rs

1use crate::*;
2
3/// Visual equality comparison for text nodes.
4///
5/// Only compares the text content; the backing signal is not considered
6/// because it does not affect visual output.
7impl PartialEq for TextNode {
8    fn eq(&self, other: &Self) -> bool {
9        self.get_content() == other.get_content()
10    }
11}
12
13/// Clones a `VirtualNode<T>` by deep-copying all fields.
14impl<T: Clone> Clone for VirtualNode<T> {
15    fn clone(&self) -> Self {
16        match self {
17            Self::Element {
18                tag,
19                attributes,
20                children,
21                key,
22                props,
23            } => Self::Element {
24                tag: tag.clone(),
25                attributes: attributes.clone(),
26                children: children.clone(),
27                key: key.clone(),
28                props: props.clone(),
29            },
30            Self::Text(text_node) => Self::Text(text_node.clone()),
31            Self::Fragment(children) => Self::Fragment(children.clone()),
32            Self::Dynamic(dynamic_node) => Self::Dynamic(dynamic_node.clone()),
33            Self::Empty => Self::Empty,
34        }
35    }
36}
37
38/// Debug formatting for `VirtualNode<T>`.
39///
40/// Skips `Dynamic` inner details and `props` for brevity.
41impl<T: std::fmt::Debug> std::fmt::Debug for VirtualNode<T> {
42    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
43        match self {
44            Self::Element {
45                tag,
46                attributes,
47                children,
48                key,
49                props,
50            } => formatter
51                .debug_struct("Element")
52                .field("tag", tag)
53                .field("attributes", attributes)
54                .field("children", children)
55                .field("key", key)
56                .field("props", props)
57                .finish(),
58            Self::Text(text_node) => formatter.debug_tuple("Text").field(text_node).finish(),
59            Self::Fragment(children) => formatter.debug_tuple("Fragment").field(children).finish(),
60            Self::Dynamic(_) => formatter.debug_tuple("Dynamic").finish(),
61            Self::Empty => formatter.debug_tuple("Empty").finish(),
62        }
63    }
64}
65
66/// Default implementation returns `VirtualNode::Empty`.
67impl<T> Default for VirtualNode<T> {
68    fn default() -> Self {
69        Self::Empty
70    }
71}
72
73/// Visual equality comparison for virtual DOM nodes.
74///
75/// Used by DynamicNode re-rendering to skip unnecessary DOM patches when
76/// the rendered output has not changed. Event attributes are always
77/// considered equal because re-binding event listeners is handled
78/// separately by the handler registry and does not affect visual output.
79/// Dynamic nodes manage their own subtree re-rendering, so two Dynamic
80/// variants are always considered equal — the inner renderer handles
81/// patching when the dynamic content actually changes.
82impl<T: PartialEq> PartialEq for VirtualNode<T> {
83    fn eq(&self, other: &Self) -> bool {
84        match (self, other) {
85            (VirtualNode::Text(old_text), VirtualNode::Text(new_text)) => old_text == new_text,
86            (
87                VirtualNode::Element {
88                    tag: old_tag,
89                    attributes: old_attrs,
90                    children: old_children,
91                    props: old_props,
92                    ..
93                },
94                VirtualNode::Element {
95                    tag: new_tag,
96                    attributes: new_attrs,
97                    children: new_children,
98                    props: new_props,
99                    ..
100                },
101            ) => {
102                old_tag == new_tag
103                    && old_attrs.len() == new_attrs.len()
104                    && old_attrs.iter().zip(new_attrs.iter()).all(
105                        |(old_attr, new_attr): (&AttributeEntry, &AttributeEntry)| {
106                            old_attr == new_attr
107                        },
108                    )
109                    && old_children.len() == new_children.len()
110                    && old_children.iter().zip(new_children.iter()).all(
111                        |(old_child, new_child): (&VirtualNode, &VirtualNode)| {
112                            old_child == new_child
113                        },
114                    )
115                    && old_props == new_props
116            }
117            (VirtualNode::Fragment(old_children), VirtualNode::Fragment(new_children)) => {
118                old_children.len() == new_children.len()
119                    && old_children.iter().zip(new_children.iter()).all(
120                        |(old_child, new_child): (&VirtualNode, &VirtualNode)| {
121                            old_child == new_child
122                        },
123                    )
124            }
125            (VirtualNode::Dynamic(_), VirtualNode::Dynamic(_)) => false,
126            (VirtualNode::Empty, VirtualNode::Empty) => true,
127            _ => false,
128        }
129    }
130}
131
132/// Provides a default empty dynamic node with a no-op render function.
133impl Default for DynamicNode {
134    fn default() -> Self {
135        let render_fn_inner: Rc<RefCell<RenderFnInner>> =
136            Rc::new(RefCell::new(RenderFnInner::new(Box::new(|| {
137                VirtualNode::Empty
138            }))));
139        Self::new(render_fn_inner, HookContext::default())
140    }
141}
142
143/// Clones a `DynamicNode` by cloning the shared references.
144impl Clone for DynamicNode {
145    fn clone(&self) -> Self {
146        let cloned: Self = Self::new(
147            self.get_render_fn().clone(),
148            self.get_hook_context().clone(),
149        );
150        cloned
151    }
152}
153
154/// Implementation of dynamic node accessor methods.
155impl DynamicNode {
156    /// Returns the hook context for this dynamic node.
157    ///
158    /// # Returns
159    ///
160    /// - `HookContext` - The hook context.
161    pub(crate) fn get_hook_context_value(&self) -> HookContext {
162        self.get_hook_context().clone()
163    }
164
165    /// Invokes the render closure and returns the produced virtual node.
166    ///
167    /// # Returns
168    ///
169    /// - `Self` - The virtual node produced by the render closure.
170    pub fn render(&self) -> VirtualNode {
171        let mut inner: RefMut<RenderFnInner> = self.get_render_fn().borrow_mut();
172        (inner.get_mut_render_fn())()
173    }
174}
175
176/// Implementation of virtual node construction and property extraction.
177impl<T> VirtualNode<T> {
178    /// Returns the tag name if this is an element or component node.
179    ///
180    /// # Returns
181    ///
182    /// - `Option<String>` - The tag name, or `None` if not an element.
183    pub fn try_get_tag_name(&self) -> Option<String> {
184        match self {
185            Self::Element { tag, .. } => match tag {
186                Tag::Element(name) => Some(name.clone()),
187                Tag::Component(name) => Some(name.clone()),
188            },
189            _ => None,
190        }
191    }
192
193    /// Returns a reference to the children of this node, if it has any.
194    ///
195    /// Returns `Some` for `Element` and `Fragment` variants, `None` otherwise.
196    ///
197    /// # Returns
198    ///
199    /// - `Option<&Vec<VirtualNode>>` - The children, or `None`.
200    pub fn try_get_children(&self) -> Option<&Vec<VirtualNode>> {
201        match self {
202            Self::Element { children, .. } => Some(children),
203            Self::Fragment(children) => Some(children),
204            _ => None,
205        }
206    }
207
208    /// Returns `true` if this node has non-empty children.
209    ///
210    /// # Returns
211    ///
212    /// - `bool` - Whether this node has children.
213    pub fn has_children(&self) -> bool {
214        self.try_get_children()
215            .is_some_and(|children: &Vec<VirtualNode>| !children.is_empty())
216    }
217
218    /// Returns a reference to the props of this node, if it has any.
219    ///
220    /// Only `Element` variants with component tags carry props.
221    ///
222    /// # Returns
223    ///
224    /// - `Option<&T>` - The props reference, or `None`.
225    pub fn try_get_props(&self) -> Option<&T> {
226        match self {
227            Self::Element { props, .. } => props.as_deref(),
228            _ => None,
229        }
230    }
231
232    /// Takes the props out of this node, leaving `None` in its place.
233    ///
234    /// # Returns
235    ///
236    /// - `Option<T>` - The props, or `None` if this node has no props.
237    pub fn try_take_props(&mut self) -> Option<T> {
238        match self {
239            Self::Element { props, .. } => props.take().map(|boxed: Box<T>| *boxed),
240            _ => None,
241        }
242    }
243
244    /// Takes the children out of this node, replacing them with an empty vector.
245    ///
246    /// Returns a `VirtualNode` (Fragment or single child) representation of the children.
247    ///
248    /// # Returns
249    ///
250    /// - `VirtualNode` - The children as a virtual node.
251    pub fn take_children(&mut self) -> VirtualNode {
252        match self {
253            Self::Element { children, .. } => {
254                let taken: Vec<VirtualNode> = take(children);
255                match taken.len() {
256                    0 => VirtualNode::Empty,
257                    1 => taken.into_iter().next().unwrap_or(VirtualNode::Empty),
258                    _ => VirtualNode::Fragment(taken),
259                }
260            }
261            Self::Fragment(children) => {
262                let taken: Vec<VirtualNode> = take(children);
263                match taken.len() {
264                    0 => VirtualNode::Empty,
265                    1 => taken.into_iter().next().unwrap_or(VirtualNode::Empty),
266                    _ => VirtualNode::Fragment(taken),
267                }
268            }
269            _ => VirtualNode::Empty,
270        }
271    }
272}
273
274/// Implementation of virtual node construction for `VirtualNode<()>`.
275impl VirtualNode<()> {
276    /// Constructs a `Self::Dynamic` from a render closure with hook context management.
277    ///
278    /// # Arguments
279    ///
280    /// - `FnMut() -> Self + 'static` - The render closure that produces
281    ///   a virtual node tree. Called on initial render and on every signal update.
282    ///
283    /// # Returns
284    ///
285    /// - `Self` - A `Self::Dynamic` wrapping the render closure
286    ///   with a fresh `HookContext`.
287    pub fn create_dynamic<F>(mut render_fn: F) -> Self
288    where
289        F: FnMut() -> Self + 'static,
290    {
291        let hook_context: HookContext = create_hook_context();
292        let mut hook_context_for_closure: HookContext = hook_context.clone();
293        let inner: Rc<RefCell<RenderFnInner>> =
294            Rc::new(RefCell::new(RenderFnInner::new(Box::new(move || {
295                hook_context_for_closure.reset_hook_index();
296                render_fn()
297            }))));
298        let dynamic_node: DynamicNode = DynamicNode::new(inner, hook_context);
299        Self::Dynamic(dynamic_node)
300    }
301
302    /// Constructs a `Self::Dynamic` for match expressions where arm hook
303    /// isolation is required. The render closure receives a `&mut HookContext`
304    /// so it can call `set_arm_changed` before each arm body.
305    ///
306    /// # Arguments
307    ///
308    /// - `FnMut(&mut HookContext) -> Self + 'static` - The render closure
309    ///   that receives a mutable reference to the hook context.
310    ///
311    /// # Returns
312    ///
313    /// - `Self` - A `Self::Dynamic` wrapping the render closure
314    ///   with a fresh `HookContext`.
315    pub fn create_dynamic_with_context<F>(mut render_fn: F) -> Self
316    where
317        F: FnMut(&mut HookContext) -> Self + 'static,
318    {
319        let hook_context: HookContext = create_hook_context();
320        let mut hook_context_for_closure: HookContext = hook_context.clone();
321        let inner: Rc<RefCell<RenderFnInner>> =
322            Rc::new(RefCell::new(RenderFnInner::new(Box::new(move || {
323                hook_context_for_closure.reset_hook_index();
324                render_fn(&mut hook_context_for_closure)
325            }))));
326        let dynamic_node: DynamicNode = DynamicNode::new(inner, hook_context);
327        Self::Dynamic(dynamic_node)
328    }
329}