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}