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}