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