Skip to main content

euv_core/vdom/node/
impl.rs

1use crate::*;
2
3/// Implementation of `From` trait for converting `usize` address into `&'static mut RenderFnInner`.
4impl From<usize> for &'static mut RenderFnInner {
5    /// Converts a memory address into a mutable reference to `RenderFnInner`.
6    ///
7    /// # Arguments
8    ///
9    /// - `usize` - The memory address of the `RenderFnInner` instance.
10    ///
11    /// # Returns
12    ///
13    /// - `&'static mut RenderFnInner` - A mutable reference to the `RenderFnInner` at the given address.
14    ///
15    /// # Safety
16    ///
17    /// - The address is guaranteed to be a valid `RenderFnInner` instance
18    ///   that was previously converted from a reference and is managed by the runtime.
19    #[inline(always)]
20    fn from(address: usize) -> Self {
21        unsafe { &mut *(address as *mut RenderFnInner) }
22    }
23}
24
25/// Implementation of `From` trait for converting `usize` address into `&'static RenderFnInner`.
26impl From<usize> for &'static RenderFnInner {
27    /// Converts a memory address into a reference to `RenderFnInner`.
28    ///
29    /// # Arguments
30    ///
31    /// - `usize` - The memory address of the `RenderFnInner` instance.
32    ///
33    /// # Returns
34    ///
35    /// - `&'static RenderFnInner` - A reference to the `RenderFnInner` at the given address.
36    ///
37    /// # Safety
38    ///
39    /// - The address is guaranteed to be a valid `RenderFnInner` instance
40    ///   that was previously converted from a reference and is managed by the runtime.
41    #[inline(always)]
42    fn from(address: usize) -> Self {
43        unsafe { &*(address as *const RenderFnInner) }
44    }
45}
46
47/// Visual equality comparison for text nodes.
48///
49/// Only compares the text content; the backing signal is not considered
50/// because it does not affect visual output.
51impl PartialEq for TextNode {
52    /// Compares two text nodes by their content.
53    ///
54    /// # Arguments
55    ///
56    /// - `&Self` - The first text node.
57    /// - `&Self` - The second text node.
58    ///
59    /// # Returns
60    ///
61    /// - `bool` - `true` if the text content is equal.
62    fn eq(&self, other: &Self) -> bool {
63        self.get_content() == other.get_content()
64    }
65}
66
67/// Visual equality comparison for virtual DOM nodes.
68///
69/// Used by DynamicNode re-rendering to skip unnecessary DOM patches when
70/// the rendered output has not changed. Event attributes are always
71/// considered equal because re-binding event listeners is handled
72/// separately by the handler registry and does not affect visual output.
73/// Dynamic nodes manage their own subtree re-rendering, so two Dynamic
74/// variants are always considered equal — the inner renderer handles
75/// patching when the dynamic content actually changes.
76impl PartialEq for VirtualNode {
77    /// Compares two virtual nodes for visual equality.
78    ///
79    /// # Arguments
80    ///
81    /// - `&Self` - The first virtual node.
82    /// - `&Self` - The second virtual node.
83    ///
84    /// # Returns
85    ///
86    /// - `bool` - `true` if the virtual nodes are visually equal.
87    fn eq(&self, other: &Self) -> bool {
88        match (self, other) {
89            (VirtualNode::Text(old_text), VirtualNode::Text(new_text)) => old_text == new_text,
90            (
91                VirtualNode::Element {
92                    tag: old_tag,
93                    attributes: old_attrs,
94                    children: old_children,
95                    ..
96                },
97                VirtualNode::Element {
98                    tag: new_tag,
99                    attributes: new_attrs,
100                    children: new_children,
101                    ..
102                },
103            ) => {
104                old_tag == new_tag
105                    && old_attrs.len() == new_attrs.len()
106                    && old_attrs
107                        .iter()
108                        .zip(new_attrs.iter())
109                        .all(|(old_attr, new_attr)| old_attr == new_attr)
110                    && old_children.len() == new_children.len()
111                    && old_children
112                        .iter()
113                        .zip(new_children.iter())
114                        .all(|(old_child, new_child)| old_child == new_child)
115            }
116            (VirtualNode::Fragment(old_children), VirtualNode::Fragment(new_children)) => {
117                old_children.len() == new_children.len()
118                    && old_children
119                        .iter()
120                        .zip(new_children.iter())
121                        .all(|(old_child, new_child)| old_child == new_child)
122            }
123            (VirtualNode::Dynamic(_), VirtualNode::Dynamic(_)) => false,
124            (VirtualNode::Empty, VirtualNode::Empty) => true,
125            _ => false,
126        }
127    }
128}
129
130/// Provides a default empty dynamic node with a no-op render function.
131impl Default for DynamicNode {
132    /// Returns a default `DynamicNode` with a no-op render function and empty hook context.
133    ///
134    /// # Returns
135    ///
136    /// - `Self` - A default dynamic node.
137    fn default() -> Self {
138        let inner: Box<RenderFnInner> = Box::new(RenderFnInner {
139            render_fn: Box::new(|| VirtualNode::Empty),
140        });
141        let node: DynamicNode = DynamicNode {
142            render_fn: Box::leak(inner) as *mut RenderFnInner,
143            hook_context: HookContext::default(),
144        };
145        node
146    }
147}
148
149/// Copies a `DynamicNode` by bitwise copy of its raw pointer and hook context.
150///
151/// A `DynamicNode` is just raw pointers; copying is a trivial bitwise copy.
152impl Clone for DynamicNode {
153    /// Returns a clone of this dynamic node sharing the same render function.
154    ///
155    /// # Returns
156    ///
157    /// - `Self` - A cloned dynamic node.
158    fn clone(&self) -> Self {
159        *self
160    }
161}
162
163/// Copies a `DynamicNode` by bitwise copy of its raw pointer and hook context.
164///
165/// A `DynamicNode` is just raw pointers; copying is a trivial bitwise copy.
166impl Copy for DynamicNode {}
167
168/// Implementation of `From` trait for converting `&DynamicNode` into `usize` address.
169impl From<&DynamicNode> for usize {
170    /// Converts a reference to `DynamicNode` into its render_fn pointer address.
171    ///
172    /// # Arguments
173    ///
174    /// - `&DynamicNode` - The reference to the dynamic node.
175    ///
176    /// # Returns
177    ///
178    /// - `usize` - The memory address of the render_fn pointer.
179    #[inline(always)]
180    fn from(node: &DynamicNode) -> Self {
181        *node.get_render_fn() as usize
182    }
183}
184
185/// Implementation of dynamic node accessor methods.
186impl DynamicNode {
187    /// Returns a mutable reference to the inner render closure state by going
188    /// through `usize` intermediate conversion.
189    ///
190    /// # Returns
191    ///
192    /// - `&'static mut RenderFnInner` - A mutable reference to the inner render closure state.
193    pub(crate) fn leak_mut(&self) -> &'static mut RenderFnInner {
194        let address: usize = self.into();
195        address.into()
196    }
197
198    /// Returns the hook context for this dynamic node.
199    ///
200    /// # Returns
201    ///
202    /// - `HookContext` - The hook context (Copy).
203    pub(crate) fn get_hook_context_value(&self) -> HookContext {
204        *self.get_hook_context()
205    }
206
207    /// Invokes the render closure and returns the produced virtual node.
208    ///
209    /// # Returns
210    ///
211    /// - `VirtualNode` - The virtual node produced by the render closure.
212    pub fn render(&self) -> VirtualNode {
213        let inner: &mut RenderFnInner = self.leak_mut();
214        (inner.get_mut_render_fn())()
215    }
216}
217
218/// Implementation of virtual node construction and property extraction.
219impl VirtualNode {
220    /// Determines whether the DOM needs to be patched when transitioning
221    /// from `old` to `new`.
222    ///
223    /// Unlike `PartialEq`, this method treats two `Dynamic` variants as
224    /// **different** so that the renderer always re-evaluates dynamic
225    /// subtrees. This is essential for route-based `match` expressions
226    /// where different pages may occupy the same DynamicNode slot.
227    ///
228    /// # Arguments
229    ///
230    /// - `&VirtualNode` - The old virtual node.
231    /// - `&VirtualNode` - The new virtual node.
232    ///
233    /// # Returns
234    ///
235    /// - `bool` - `true` if the DOM needs to be patched.
236    pub fn needs_patch(old: &VirtualNode, new: &VirtualNode) -> bool {
237        match (old, new) {
238            (VirtualNode::Text(old_text), VirtualNode::Text(new_text)) => {
239                old_text.get_content() != new_text.get_content()
240            }
241            (
242                VirtualNode::Element {
243                    tag: old_tag,
244                    attributes: old_attrs,
245                    children: old_children,
246                    key: _old_key,
247                },
248                VirtualNode::Element {
249                    tag: new_tag,
250                    attributes: new_attrs,
251                    children: new_children,
252                    key: _new_key,
253                },
254            ) => {
255                if old_tag != new_tag {
256                    return true;
257                }
258                if old_attrs.len() != new_attrs.len() {
259                    return true;
260                }
261                for (old_attr, new_attr) in old_attrs.iter().zip(new_attrs.iter()) {
262                    if old_attr.get_name() != new_attr.get_name()
263                        || old_attr.get_value() != new_attr.get_value()
264                    {
265                        return true;
266                    }
267                }
268                if old_children.len() != new_children.len() {
269                    return true;
270                }
271                for (old_child, new_child) in old_children.iter().zip(new_children.iter()) {
272                    if Self::needs_patch(old_child, new_child) {
273                        return true;
274                    }
275                }
276                false
277            }
278            (VirtualNode::Fragment(old_children), VirtualNode::Fragment(new_children)) => {
279                if old_children.len() != new_children.len() {
280                    return true;
281                }
282                for (old_child, new_child) in old_children.iter().zip(new_children.iter()) {
283                    if Self::needs_patch(old_child, new_child) {
284                        return true;
285                    }
286                }
287                false
288            }
289            (VirtualNode::Dynamic(_), VirtualNode::Dynamic(_)) => false,
290            (VirtualNode::Empty, VirtualNode::Empty) => false,
291            _ => true,
292        }
293    }
294
295    /// Creates a new element node with the given tag name.
296    ///
297    /// # Arguments
298    ///
299    /// - `&str` - The tag name for the element.
300    ///
301    /// # Returns
302    ///
303    /// - `Self` - A new element virtual node.
304    pub fn get_element_node(tag_name: &str) -> Self {
305        VirtualNode::Element {
306            tag: Tag::Element(tag_name.to_string()),
307            attributes: Vec::new(),
308            children: Vec::new(),
309            key: None,
310        }
311    }
312
313    /// Creates a new text node with the given content.
314    ///
315    /// # Arguments
316    ///
317    /// - `&str` - The text content.
318    ///
319    /// # Returns
320    ///
321    /// - `Self` - A new text virtual node.
322    pub fn get_text_node(content: &str) -> Self {
323        VirtualNode::Text(TextNode::new(content.to_string(), None))
324    }
325
326    /// Adds an attribute to this node if it is an element.
327    ///
328    /// # Arguments
329    ///
330    /// - `&str` - The attribute name.
331    /// - `AttributeValue` - The attribute value.
332    ///
333    /// # Returns
334    ///
335    /// - `Self` - This node with the attribute added.
336    pub fn with_attribute(mut self, name: &str, value: AttributeValue) -> Self {
337        if let VirtualNode::Element {
338            ref mut attributes, ..
339        } = self
340        {
341            attributes.push(AttributeEntry::new(name.to_string(), value));
342        }
343        self
344    }
345
346    /// Adds a child node to this node if it is an element.
347    ///
348    /// # Arguments
349    ///
350    /// - `VirtualNode` - The child node to add.
351    ///
352    /// # Returns
353    ///
354    /// - `Self` - This node with the child added.
355    pub fn with_child(mut self, child: VirtualNode) -> Self {
356        if let VirtualNode::Element {
357            ref mut children, ..
358        } = self
359        {
360            children.push(child);
361        }
362        self
363    }
364
365    /// Returns true if this node is a component node.
366    ///
367    /// # Returns
368    ///
369    /// - `bool` - `true` if this is a component node.
370    pub fn is_component(&self) -> bool {
371        matches!(
372            self,
373            VirtualNode::Element {
374                tag: Tag::Component(_),
375                ..
376            }
377        )
378    }
379
380    /// Returns the tag name if this is an element or component node.
381    ///
382    /// # Returns
383    ///
384    /// - `Option<String>` - The tag name, or `None` if not an element node.
385    pub fn tag_name(&self) -> Option<String> {
386        match self {
387            VirtualNode::Element { tag, .. } => match tag {
388                Tag::Element(name) => Some(name.clone()),
389                Tag::Component(name) => Some(name.clone()),
390            },
391            _ => None,
392        }
393    }
394
395    /// Extracts a string property from this node if it is an element with the named attribute.
396    ///
397    /// # Arguments
398    ///
399    /// - `&str` - The attribute name to look up.
400    ///
401    /// # Returns
402    ///
403    /// - `Option<String>` - The attribute value as a string, or `None` if not found.
404    pub fn try_get_prop(&self, name: &str) -> Option<String> {
405        if let VirtualNode::Element { attributes, .. } = self {
406            for attr in attributes {
407                if attr.get_name() == name {
408                    match attr.get_value() {
409                        AttributeValue::Text(value) => return Some(value.clone()),
410                        AttributeValue::Signal(signal) => return Some(signal.get()),
411                        AttributeValue::Dynamic(value) => return Some(value.clone()),
412                        _ => {}
413                    }
414                }
415            }
416        }
417        None
418    }
419
420    /// Extracts a typed property from this node by parsing the attribute value string.
421    ///
422    /// Supports `Text`, `Signal`, and `Dynamic` attribute values. The string
423    /// representation is parsed into the target type `T` via `FromStr`.
424    ///
425    /// # Arguments
426    ///
427    /// - `&str` - The attribute name to look up.
428    ///
429    /// # Returns
430    ///
431    /// - `Option<T>` - The parsed value, or `None` if not found or parsing fails.
432    pub fn try_get_typed_prop<T>(&self, name: &str) -> Option<T>
433    where
434        T: std::str::FromStr,
435    {
436        if let VirtualNode::Element { attributes, .. } = self {
437            for attr in attributes {
438                if attr.get_name() == name {
439                    let raw: String = match attr.get_value() {
440                        AttributeValue::Text(value) => value.clone(),
441                        AttributeValue::Signal(signal) => signal.get(),
442                        AttributeValue::Dynamic(value) => value.clone(),
443                        _ => continue,
444                    };
445                    return raw.parse::<T>().ok();
446                }
447            }
448        }
449        None
450    }
451
452    /// Extracts a signal property from this node if it is an element with the named attribute.
453    ///
454    /// Returns the raw `Signal<String>` so components can reactively read the current value
455    /// and subscribe to future changes, rather than receiving a snapshot string.
456    ///
457    /// # Arguments
458    ///
459    /// - `&str` - The attribute name to look up.
460    ///
461    /// # Returns
462    ///
463    /// - `Option<Signal<String>>` - The signal if found, or `None`.
464    pub fn try_get_signal_prop(&self, name: &str) -> Option<Signal<String>> {
465        if let VirtualNode::Element { attributes, .. } = self {
466            for attr in attributes {
467                if attr.get_name() == name
468                    && let AttributeValue::Signal(signal) = attr.get_value()
469                {
470                    return Some(*signal);
471                }
472            }
473        }
474        None
475    }
476
477    /// Extracts children from this node if it is an element.
478    ///
479    /// # Returns
480    ///
481    /// - `Vec<VirtualNode>` - The children, or an empty vec if not an element.
482    pub fn get_children(&self) -> Vec<VirtualNode> {
483        if let VirtualNode::Element { children, .. } = self {
484            children.clone()
485        } else {
486            Vec::new()
487        }
488    }
489
490    /// Extracts text content from this node.
491    ///
492    /// # Returns
493    ///
494    /// - `Option<String>` - The text content, or `None` if not a text node.
495    pub fn try_get_text(&self) -> Option<String> {
496        match self {
497            VirtualNode::Text(text_node) => Some(text_node.get_content().clone()),
498            VirtualNode::Element { children, .. } => {
499                children.first().and_then(VirtualNode::try_get_text)
500            }
501            _ => None,
502        }
503    }
504
505    /// Extracts an event handler from this node if it is an element with the named event attribute.
506    ///
507    /// # Arguments
508    ///
509    /// - `&str` - The event name to look up.
510    ///
511    /// # Returns
512    ///
513    /// - `Option<NativeEventHandler>` - The event handler if found, or `None`.
514    pub fn try_get_event(&self, name: &str) -> Option<NativeEventHandler> {
515        if let VirtualNode::Element { attributes, .. } = self {
516            for attr in attributes {
517                if attr.get_name() == name
518                    && let AttributeValue::Event(handler) = attr.get_value()
519                {
520                    return Some(handler.clone());
521                }
522            }
523        }
524        None
525    }
526
527    /// Extracts an event handler from this node by a custom attribute name.
528    ///
529    /// # Arguments
530    ///
531    /// - `&str` - The custom attribute name to look up.
532    ///
533    /// # Returns
534    ///
535    /// - `Option<NativeEventHandler>` - The event handler if found, or `None`.
536    pub fn try_get_callback(&self, name: &str) -> Option<NativeEventHandler> {
537        if let VirtualNode::Element { attributes, .. } = self {
538            for attr in attributes {
539                if attr.get_name() == name
540                    && let AttributeValue::Event(handler) = attr.get_value()
541                {
542                    return Some(handler.clone());
543                }
544            }
545        }
546        None
547    }
548}