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<UnsafeCell<RenderFnInner>> =
136 Rc::new(UnsafeCell::new(RenderFnInner::new(Box::new(|| {
137 VirtualNode::Empty
138 }))));
139 Self::new(render_fn_inner, HookContext::default())
140 }
141}
142
143/// Implementation of dynamic node accessor methods.
144impl DynamicNode {
145 /// Invokes the render closure and returns the produced virtual node.
146 ///
147 /// # Safety
148 ///
149 /// Must only be called from the main thread. Guaranteed in WASM
150 /// single-threaded context. No concurrent access is possible.
151 ///
152 /// # Returns
153 ///
154 /// - `VirtualNode` - The virtual node produced by the render closure.
155 pub fn render(&self) -> VirtualNode {
156 let inner: &mut RenderFnInner = unsafe { &mut *self.get_render_fn().get() };
157 (inner.get_mut_render_fn())()
158 }
159}
160
161/// Implementation of virtual node construction and property extraction.
162impl<T> VirtualNode<T> {
163 /// Returns the tag name if this is an element or component node.
164 ///
165 /// # Returns
166 ///
167 /// - `Option<String>` - The tag name, or `None` if not an element.
168 pub fn try_get_tag_name(&self) -> Option<String> {
169 match self {
170 Self::Element { tag, .. } => match tag {
171 Tag::Element(name) => Some(name.clone()),
172 Tag::Component(name) => Some(name.clone()),
173 },
174 _ => None,
175 }
176 }
177
178 /// Returns a reference to the children of this node, if it has any.
179 ///
180 /// Returns `Some` for `Element` and `Fragment` variants, `None` otherwise.
181 ///
182 /// # Returns
183 ///
184 /// - `Option<&Vec<VirtualNode>>` - The children, or `None`.
185 pub fn try_get_children(&self) -> Option<&Vec<VirtualNode>> {
186 match self {
187 Self::Element { children, .. } => Some(children),
188 Self::Fragment(children) => Some(children),
189 _ => None,
190 }
191 }
192
193 /// Returns `true` if this node has non-empty children.
194 ///
195 /// # Returns
196 ///
197 /// - `bool` - Whether this node has children.
198 pub fn has_children(&self) -> bool {
199 self.try_get_children()
200 .is_some_and(|children: &Vec<VirtualNode>| !children.is_empty())
201 }
202
203 /// Clones the props of this node.
204 ///
205 /// # Returns
206 ///
207 /// - `Option<T>` - The cloned props, or `None` if this node has no props.
208 pub fn try_get_props(&self) -> Option<T>
209 where
210 T: Clone,
211 {
212 match self {
213 Self::Element { props, .. } => props.as_deref().cloned(),
214 _ => None,
215 }
216 }
217
218 /// Returns the children of this node as a virtual node.
219 ///
220 /// Returns `VirtualNode::Empty` when there are no children, a single child
221 /// when there is exactly one, or `VirtualNode::Fragment` when there are
222 /// multiple children.
223 ///
224 /// # Returns
225 ///
226 /// - `VirtualNode` - The children as a virtual node.
227 pub fn try_get_child_node(&self) -> VirtualNode {
228 match self.try_get_children() {
229 Some(children) => match children.len() {
230 0 => VirtualNode::Empty,
231 1 => children.first().cloned().unwrap_or_default(),
232 _ => VirtualNode::Fragment(children.clone()),
233 },
234 None => VirtualNode::Empty,
235 }
236 }
237}
238
239/// Implementation of virtual node construction for `VirtualNode<()>`.
240impl VirtualNode<()> {
241 /// Constructs a `Self::Dynamic` from a render closure with hook context management.
242 ///
243 /// # Arguments
244 ///
245 /// - `FnMut() -> Self + 'static` - The render closure that produces
246 /// a virtual node tree. Called on initial render and on every signal update.
247 ///
248 /// # Returns
249 ///
250 /// - `Self` - A `Self::Dynamic` wrapping the render closure
251 /// with a fresh `HookContext`.
252 pub fn create_dynamic<F>(mut render_fn: F) -> Self
253 where
254 F: FnMut() -> Self + 'static,
255 {
256 let hook_context: HookContext = create_hook_context();
257 let mut hook_context_for_closure: HookContext = hook_context.clone();
258 let inner: Rc<UnsafeCell<RenderFnInner>> =
259 Rc::new(UnsafeCell::new(RenderFnInner::new(Box::new(move || {
260 hook_context_for_closure.reset_hook_index();
261 render_fn()
262 }))));
263 Self::Dynamic(DynamicNode::new(inner, hook_context))
264 }
265
266 /// Constructs a `Self::Dynamic` for match expressions where arm hook
267 /// isolation is required. The render closure receives a `&mut HookContext`
268 /// so it can call `set_arm_changed` before each arm body.
269 ///
270 /// # Arguments
271 ///
272 /// - `FnMut(&mut HookContext) -> Self + 'static` - The render closure
273 /// that receives a mutable reference to the hook context.
274 ///
275 /// # Returns
276 ///
277 /// - `Self` - A `Self::Dynamic` wrapping the render closure
278 /// with a fresh `HookContext`.
279 pub fn create_dynamic_with_context<F>(mut render_fn: F) -> Self
280 where
281 F: FnMut(&mut HookContext) -> Self + 'static,
282 {
283 let hook_context: HookContext = create_hook_context();
284 let mut hook_context_for_closure: HookContext = hook_context.clone();
285 let inner: Rc<UnsafeCell<RenderFnInner>> =
286 Rc::new(UnsafeCell::new(RenderFnInner::new(Box::new(move || {
287 hook_context_for_closure.reset_hook_index();
288 render_fn(&mut hook_context_for_closure)
289 }))));
290 Self::Dynamic(DynamicNode::new(inner, hook_context))
291 }
292}