Skip to main content

kozan_core/dom/
node.rs

1// Node flags — Chrome-style packed u32 bitfield.
2//
3// Like Chrome's `node_flags_` (32 bits), this stores:
4// - Node type (4 bits) — Element, Text, Document, etc.
5// - Capability flags — is_container, is_connected
6// - Dirty flags — style, layout, paint, tree
7// - Focus state
8//
9// Type checking is a mask operation, not a virtual call or enum match.
10// `is_element()` = one AND + one CMP. Same as Chrome.
11
12use core::any::TypeId;
13
14// ============================================================
15// NodeFlags — 32-bit packed bitfield
16// ============================================================
17
18/// Packed node metadata. 4 bytes per node.
19///
20/// Layout (matching Chrome's design):
21/// ```text
22/// Bits  0-3:  NodeType (4 bits, 16 possible values)
23/// Bit   4:    is_container (can have children)
24/// Bit   5:    is_connected (attached to a document tree)
25/// Bit   6:    needs_style_recalc
26/// Bit   7:    child_needs_style_recalc
27/// Bit   8:    needs_layout
28/// Bit   9:    child_needs_layout
29/// Bit  10:    needs_paint
30/// Bit  11:    child_needs_paint
31/// Bit  12:    is_focusable
32/// Bit  13:    is_focused
33/// Bit  14:    tree_structure_changed
34/// Bits 15-31: reserved for future use
35/// ```
36#[derive(Copy, Clone, Debug)]
37pub struct NodeFlags(u32);
38
39// Bit masks.
40const NODE_TYPE_MASK: u32 = 0b1111; // bits 0-3
41const 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/// Node type constants (stored in bits 0-3).
54/// Values match the DOM spec / Chrome's `NodeType` enum.
55#[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    /// Convert from the raw 4-bit value. Returns None for unknown types.
68    #[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    // ---- Constructors for each node kind ----
84
85    /// Flags for an element node (container, not connected).
86    #[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    /// Flags for a text node (leaf, not container).
96    #[must_use]
97    pub fn text() -> Self {
98        Self(NodeType::Text as u32)
99    }
100
101    /// Flags for the document root node (container).
102    #[must_use]
103    pub fn document() -> Self {
104        Self((NodeType::Document as u32) | IS_CONTAINER | IS_CONNECTED)
105    }
106
107    // ---- Type queries (inline, no virtual dispatch) ----
108
109    /// The DOM node type.
110    #[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    /// Is this an element node? (single AND + CMP)
118    #[inline]
119    #[must_use]
120    pub fn is_element(self) -> bool {
121        (self.0 & NODE_TYPE_MASK) == NodeType::Element as u32
122    }
123
124    /// Is this a text node?
125    #[inline]
126    #[must_use]
127    pub fn is_text(self) -> bool {
128        (self.0 & NODE_TYPE_MASK) == NodeType::Text as u32
129    }
130
131    /// Is this the document node?
132    #[inline]
133    #[must_use]
134    pub fn is_document(self) -> bool {
135        (self.0 & NODE_TYPE_MASK) == NodeType::Document as u32
136    }
137
138    /// Can this node have children? (Element, Document, `DocumentFragment`)
139    #[inline]
140    #[must_use]
141    pub fn is_container(self) -> bool {
142        (self.0 & IS_CONTAINER) != 0
143    }
144
145    /// Is this node connected to a document tree?
146    #[inline]
147    #[must_use]
148    pub fn is_connected(self) -> bool {
149        (self.0 & IS_CONNECTED) != 0
150    }
151
152    // ---- Focus ----
153
154    #[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    // ---- Connection ----
175
176    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    // ---- Dirty flags ----
185
186    #[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    /// Mark this node as needing style recalculation.
223    /// Cascades to layout + paint.
224    pub fn mark_style_dirty(&mut self) {
225        self.0 |= NEEDS_STYLE_RECALC | NEEDS_LAYOUT | NEEDS_PAINT;
226    }
227
228    /// Mark this node as needing layout. Cascades to paint.
229    pub fn mark_layout_dirty(&mut self) {
230        self.0 |= NEEDS_LAYOUT | NEEDS_PAINT;
231    }
232
233    /// Mark this node as needing repaint only.
234    pub fn mark_paint_dirty(&mut self) {
235        self.0 |= NEEDS_PAINT;
236    }
237
238    /// Mark tree structure changed.
239    pub fn mark_tree_dirty(&mut self) {
240        self.0 |= TREE_CHANGED | NEEDS_LAYOUT | NEEDS_PAINT;
241    }
242
243    /// Mark that a child needs style recalculation.
244    pub fn mark_child_style_dirty(&mut self) {
245        self.0 |= CHILD_NEEDS_STYLE;
246    }
247
248    /// Mark that a child needs layout.
249    pub fn mark_child_layout_dirty(&mut self) {
250        self.0 |= CHILD_NEEDS_LAYOUT;
251    }
252
253    /// Mark that a child needs paint.
254    pub fn mark_child_paint_dirty(&mut self) {
255        self.0 |= CHILD_NEEDS_PAINT;
256    }
257
258    /// Is any dirty flag set on this node or its children?
259    #[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    /// Clear all dirty flags after a full pipeline pass.
274    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    /// Get the raw u32 value.
285    #[inline]
286    #[must_use]
287    pub fn raw(self) -> u32 {
288        self.0
289    }
290}
291
292// ============================================================
293// NodeMeta — per-node metadata in parallel storage
294// ============================================================
295
296/// Per-node metadata stored in `Storage<NodeMeta>`.
297///
298/// Combines `NodeFlags` (4 bytes) + data `TypeId` (16 bytes on 64-bit).
299/// The `TypeId` identifies which column in `DataStorage` holds this node's
300/// element-specific data (e.g., `TypeId::of::<ButtonData>()`).
301#[derive(Copy, Clone)]
302pub struct NodeMeta {
303    /// Packed flags: node type, container, dirty, focus, connection.
304    pub flags: NodeFlags,
305    /// `TypeId` of the data in `DataStorage`. `TypeId::of::<()>()` for no data.
306    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()); // text nodes can't have children
337    }
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); // must be u32
405    }
406}