1use core::any::TypeId;
13
14#[derive(Copy, Clone, Debug)]
37pub struct NodeFlags(u32);
38
39const NODE_TYPE_MASK: u32 = 0b1111; const IS_CONTAINER: u32 = 1 << 4;
42const IS_CONNECTED: u32 = 1 << 5;
43const NEEDS_STYLE_RECALC: u32 = 1 << 6;
44const CHILD_NEEDS_STYLE: u32 = 1 << 7;
45const NEEDS_LAYOUT: u32 = 1 << 8;
46const CHILD_NEEDS_LAYOUT: u32 = 1 << 9;
47const NEEDS_PAINT: u32 = 1 << 10;
48const CHILD_NEEDS_PAINT: u32 = 1 << 11;
49const IS_FOCUSABLE: u32 = 1 << 12;
50const IS_FOCUSED: u32 = 1 << 13;
51const TREE_CHANGED: u32 = 1 << 14;
52
53#[derive(Copy, Clone, Debug, PartialEq, Eq)]
56#[repr(u8)]
57pub enum NodeType {
58 Element = 1,
59 Text = 3,
60 Comment = 8,
61 Document = 9,
62 DocumentType = 10,
63 DocumentFragment = 11,
64}
65
66impl NodeType {
67 #[must_use]
69 pub fn from_raw(value: u32) -> Option<Self> {
70 match value {
71 1 => Some(Self::Element),
72 3 => Some(Self::Text),
73 8 => Some(Self::Comment),
74 9 => Some(Self::Document),
75 10 => Some(Self::DocumentType),
76 11 => Some(Self::DocumentFragment),
77 _ => None,
78 }
79 }
80}
81
82impl NodeFlags {
83 #[must_use]
87 pub fn element(focusable: bool) -> Self {
88 let mut flags = (NodeType::Element as u32) | IS_CONTAINER;
89 if focusable {
90 flags |= IS_FOCUSABLE;
91 }
92 Self(flags)
93 }
94
95 #[must_use]
97 pub fn text() -> Self {
98 Self(NodeType::Text as u32)
99 }
100
101 #[must_use]
103 pub fn document() -> Self {
104 Self((NodeType::Document as u32) | IS_CONTAINER | IS_CONNECTED)
105 }
106
107 #[inline]
111 #[must_use]
112 pub fn node_type(self) -> NodeType {
113 NodeType::from_raw(self.0 & NODE_TYPE_MASK)
114 .expect("NodeKey always stores a valid NodeType in its low bits")
115 }
116
117 #[inline]
119 #[must_use]
120 pub fn is_element(self) -> bool {
121 (self.0 & NODE_TYPE_MASK) == NodeType::Element as u32
122 }
123
124 #[inline]
126 #[must_use]
127 pub fn is_text(self) -> bool {
128 (self.0 & NODE_TYPE_MASK) == NodeType::Text as u32
129 }
130
131 #[inline]
133 #[must_use]
134 pub fn is_document(self) -> bool {
135 (self.0 & NODE_TYPE_MASK) == NodeType::Document as u32
136 }
137
138 #[inline]
140 #[must_use]
141 pub fn is_container(self) -> bool {
142 (self.0 & IS_CONTAINER) != 0
143 }
144
145 #[inline]
147 #[must_use]
148 pub fn is_connected(self) -> bool {
149 (self.0 & IS_CONNECTED) != 0
150 }
151
152 #[inline]
155 #[must_use]
156 pub fn is_focusable(self) -> bool {
157 (self.0 & IS_FOCUSABLE) != 0
158 }
159
160 #[inline]
161 #[must_use]
162 pub fn is_focused(self) -> bool {
163 (self.0 & IS_FOCUSED) != 0
164 }
165
166 pub fn set_focused(&mut self, focused: bool) {
167 if focused {
168 self.0 |= IS_FOCUSED;
169 } else {
170 self.0 &= !IS_FOCUSED;
171 }
172 }
173
174 pub fn set_connected(&mut self, connected: bool) {
177 if connected {
178 self.0 |= IS_CONNECTED;
179 } else {
180 self.0 &= !IS_CONNECTED;
181 }
182 }
183
184 #[inline]
187 #[must_use]
188 pub fn needs_style_recalc(self) -> bool {
189 (self.0 & NEEDS_STYLE_RECALC) != 0
190 }
191
192 #[inline]
193 #[must_use]
194 pub fn child_needs_style_recalc(self) -> bool {
195 (self.0 & CHILD_NEEDS_STYLE) != 0
196 }
197
198 #[inline]
199 #[must_use]
200 pub fn needs_layout(self) -> bool {
201 (self.0 & NEEDS_LAYOUT) != 0
202 }
203
204 #[inline]
205 #[must_use]
206 pub fn child_needs_layout(self) -> bool {
207 (self.0 & CHILD_NEEDS_LAYOUT) != 0
208 }
209
210 #[inline]
211 #[must_use]
212 pub fn needs_paint(self) -> bool {
213 (self.0 & NEEDS_PAINT) != 0
214 }
215
216 #[inline]
217 #[must_use]
218 pub fn child_needs_paint(self) -> bool {
219 (self.0 & CHILD_NEEDS_PAINT) != 0
220 }
221
222 pub fn mark_style_dirty(&mut self) {
225 self.0 |= NEEDS_STYLE_RECALC | NEEDS_LAYOUT | NEEDS_PAINT;
226 }
227
228 pub fn mark_layout_dirty(&mut self) {
230 self.0 |= NEEDS_LAYOUT | NEEDS_PAINT;
231 }
232
233 pub fn mark_paint_dirty(&mut self) {
235 self.0 |= NEEDS_PAINT;
236 }
237
238 pub fn mark_tree_dirty(&mut self) {
240 self.0 |= TREE_CHANGED | NEEDS_LAYOUT | NEEDS_PAINT;
241 }
242
243 pub fn mark_child_style_dirty(&mut self) {
245 self.0 |= CHILD_NEEDS_STYLE;
246 }
247
248 pub fn mark_child_layout_dirty(&mut self) {
250 self.0 |= CHILD_NEEDS_LAYOUT;
251 }
252
253 pub fn mark_child_paint_dirty(&mut self) {
255 self.0 |= CHILD_NEEDS_PAINT;
256 }
257
258 #[inline]
260 #[must_use]
261 pub fn is_dirty(self) -> bool {
262 (self.0
263 & (NEEDS_STYLE_RECALC
264 | NEEDS_LAYOUT
265 | NEEDS_PAINT
266 | CHILD_NEEDS_STYLE
267 | CHILD_NEEDS_LAYOUT
268 | CHILD_NEEDS_PAINT
269 | TREE_CHANGED))
270 != 0
271 }
272
273 pub fn clear_all_dirty(&mut self) {
275 self.0 &= !(NEEDS_STYLE_RECALC
276 | CHILD_NEEDS_STYLE
277 | NEEDS_LAYOUT
278 | CHILD_NEEDS_LAYOUT
279 | NEEDS_PAINT
280 | CHILD_NEEDS_PAINT
281 | TREE_CHANGED);
282 }
283
284 #[inline]
286 #[must_use]
287 pub fn raw(self) -> u32 {
288 self.0
289 }
290}
291
292#[derive(Copy, Clone)]
302pub struct NodeMeta {
303 pub flags: NodeFlags,
305 pub data_type_id: TypeId,
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312
313 #[test]
314 fn element_flags() {
315 let flags = NodeFlags::element(false);
316 assert!(flags.is_element());
317 assert!(!flags.is_text());
318 assert!(!flags.is_document());
319 assert!(flags.is_container());
320 assert!(!flags.is_focusable());
321 }
322
323 #[test]
324 fn focusable_element() {
325 let flags = NodeFlags::element(true);
326 assert!(flags.is_element());
327 assert!(flags.is_focusable());
328 assert!(flags.is_container());
329 }
330
331 #[test]
332 fn text_flags() {
333 let flags = NodeFlags::text();
334 assert!(flags.is_text());
335 assert!(!flags.is_element());
336 assert!(!flags.is_container()); }
338
339 #[test]
340 fn document_flags() {
341 let flags = NodeFlags::document();
342 assert!(flags.is_document());
343 assert!(flags.is_container());
344 assert!(flags.is_connected());
345 }
346
347 #[test]
348 fn dirty_flags_cascade() {
349 let mut flags = NodeFlags::element(false);
350 assert!(!flags.is_dirty());
351
352 flags.mark_style_dirty();
353 assert!(flags.needs_style_recalc());
354 assert!(flags.needs_layout());
355 assert!(flags.needs_paint());
356 assert!(flags.is_dirty());
357
358 flags.clear_all_dirty();
359 assert!(!flags.is_dirty());
360 assert!(!flags.needs_style_recalc());
361 assert!(!flags.needs_layout());
362 assert!(!flags.needs_paint());
363 }
364
365 #[test]
366 fn child_dirty_propagation() {
367 let mut flags = NodeFlags::element(false);
368 flags.mark_child_style_dirty();
369 assert!(flags.child_needs_style_recalc());
370 assert!(flags.is_dirty());
371 }
372
373 #[test]
374 fn focus_state() {
375 let mut flags = NodeFlags::element(true);
376 assert!(!flags.is_focused());
377 flags.set_focused(true);
378 assert!(flags.is_focused());
379 flags.set_focused(false);
380 assert!(!flags.is_focused());
381 }
382
383 #[test]
384 fn node_type_values_match_dom_spec() {
385 assert_eq!(NodeType::Element as u8, 1);
386 assert_eq!(NodeType::Text as u8, 3);
387 assert_eq!(NodeType::Comment as u8, 8);
388 assert_eq!(NodeType::Document as u8, 9);
389 assert_eq!(NodeType::DocumentType as u8, 10);
390 assert_eq!(NodeType::DocumentFragment as u8, 11);
391 }
392
393 #[test]
394 fn node_type_from_raw() {
395 assert_eq!(NodeType::from_raw(1), Some(NodeType::Element));
396 assert_eq!(NodeType::from_raw(3), Some(NodeType::Text));
397 assert_eq!(NodeType::from_raw(9), Some(NodeType::Document));
398 assert_eq!(NodeType::from_raw(0), None);
399 assert_eq!(NodeType::from_raw(99), None);
400 }
401
402 #[test]
403 fn size_of_node_flags() {
404 assert_eq!(core::mem::size_of::<NodeFlags>(), 4); }
406}