Skip to main content

azul_core/
styled_dom.rs

1use alloc::{boxed::Box, collections::btree_map::BTreeMap, string::String, vec::Vec};
2use core::{
3    fmt,
4    hash::{Hash, Hasher},
5};
6
7use azul_css::{
8    css::{Css, CssPath},
9    props::{
10        basic::{StyleFontFamily, StyleFontFamilyVec, StyleFontSize},
11        property::{
12            BoxDecorationBreakValue, BreakInsideValue, CaretAnimationDurationValue,
13            CaretColorValue, ColumnCountValue, ColumnFillValue, ColumnRuleColorValue,
14            ColumnRuleStyleValue, ColumnRuleWidthValue, ColumnSpanValue, ColumnWidthValue,
15            ContentValue, CounterIncrementValue, CounterResetValue, CssProperty, CssPropertyType,
16            RelayoutScope,
17            FlowFromValue, FlowIntoValue, LayoutAlignContentValue, LayoutAlignItemsValue,
18            LayoutAlignSelfValue, LayoutBorderBottomWidthValue, LayoutBorderLeftWidthValue,
19            LayoutBorderRightWidthValue, LayoutBorderTopWidthValue, LayoutBoxSizingValue,
20            LayoutClearValue, LayoutColumnGapValue, LayoutDisplayValue, LayoutFlexBasisValue,
21            LayoutFlexDirectionValue, LayoutFlexGrowValue, LayoutFlexShrinkValue,
22            LayoutFlexWrapValue, LayoutFloatValue, LayoutGapValue, LayoutGridAutoColumnsValue,
23            LayoutGridAutoFlowValue, LayoutGridAutoRowsValue, LayoutGridColumnValue,
24            LayoutGridRowValue, LayoutGridTemplateColumnsValue, LayoutGridTemplateRowsValue,
25            LayoutHeightValue, LayoutInsetBottomValue, LayoutJustifyContentValue,
26            LayoutJustifyItemsValue, LayoutJustifySelfValue, LayoutLeftValue,
27            LayoutMarginBottomValue, LayoutMarginLeftValue, LayoutMarginRightValue,
28            LayoutMarginTopValue, LayoutMaxHeightValue, LayoutMaxWidthValue, LayoutMinHeightValue,
29            LayoutMinWidthValue, LayoutOverflowValue, LayoutPaddingBottomValue,
30            LayoutPaddingLeftValue, LayoutPaddingRightValue, LayoutPaddingTopValue,
31            LayoutPositionValue, LayoutRightValue, LayoutRowGapValue, LayoutScrollbarWidthValue,
32            LayoutTextJustifyValue, LayoutTopValue, LayoutWidthValue, LayoutWritingModeValue,
33            LayoutZIndexValue, OrphansValue, PageBreakValue, ScrollbarStyleValue,
34            SelectionBackgroundColorValue, SelectionColorValue, ShapeImageThresholdValue,
35            ShapeMarginValue, ShapeOutsideValue, StringSetValue, StyleBackfaceVisibilityValue,
36            StyleBackgroundContentVecValue, StyleBackgroundPositionVecValue,
37            StyleBackgroundRepeatVecValue, StyleBackgroundSizeVecValue,
38            StyleBorderBottomColorValue, StyleBorderBottomLeftRadiusValue,
39            StyleBorderBottomRightRadiusValue, StyleBorderBottomStyleValue,
40            StyleBorderLeftColorValue, StyleBorderLeftStyleValue, StyleBorderRightColorValue,
41            StyleBorderRightStyleValue, StyleBorderTopColorValue, StyleBorderTopLeftRadiusValue,
42            StyleBorderTopRightRadiusValue, StyleBorderTopStyleValue, StyleBoxShadowValue,
43            StyleCursorValue, StyleDirectionValue, StyleFilterVecValue, StyleFontFamilyVecValue,
44            StyleFontSizeValue, StyleFontValue, StyleHyphensValue, StyleLetterSpacingValue,
45            StyleLineHeightValue, StyleMixBlendModeValue, StyleOpacityValue,
46            StylePerspectiveOriginValue, StyleScrollbarColorValue, StyleTabSizeValue,
47            StyleTextAlignValue, StyleTextColorValue, StyleTransformOriginValue,
48            StyleTransformVecValue, StyleVisibilityValue, StyleWhiteSpaceValue,
49            StyleWordSpacingValue, WidowsValue,
50        },
51        style::StyleTextColor,
52    },
53    AzString,
54};
55
56use crate::{
57    callbacks::Update,
58    dom::{Dom, DomId, NodeData, NodeDataVec, OptionTabIndex, TabIndex, TagId},
59    id::{
60        Node, NodeDataContainer, NodeDataContainerRef, NodeDataContainerRefMut, NodeHierarchy,
61        NodeId,
62    },
63    menu::Menu,
64    prop_cache::{CssPropertyCache, CssPropertyCachePtr},
65    refany::RefAny,
66    resources::{Au, ImageCache, ImageRef, ImmediateFontId, RendererResources},
67    style::{
68        construct_html_cascade_tree, matches_html_element, rule_ends_with, CascadeInfo,
69        CascadeInfoVec,
70    },
71    FastBTreeSet, FastHashMap,
72};
73
74#[repr(C)]
75#[derive(Debug, Clone, PartialEq, Hash, PartialOrd, Eq, Ord)]
76pub struct ChangedCssProperty {
77    pub previous_state: StyledNodeState,
78    pub previous_prop: CssProperty,
79    pub current_state: StyledNodeState,
80    pub current_prop: CssProperty,
81}
82
83impl_option!(
84    ChangedCssProperty,
85    OptionChangedCssProperty,
86    copy = false,
87    [Debug, Clone, PartialEq, Hash, PartialOrd, Eq, Ord]
88);
89
90impl_vec!(ChangedCssProperty, ChangedCssPropertyVec, ChangedCssPropertyVecDestructor, ChangedCssPropertyVecDestructorType, ChangedCssPropertyVecSlice, OptionChangedCssProperty);
91impl_vec_debug!(ChangedCssProperty, ChangedCssPropertyVec);
92impl_vec_partialord!(ChangedCssProperty, ChangedCssPropertyVec);
93impl_vec_clone!(
94    ChangedCssProperty,
95    ChangedCssPropertyVec,
96    ChangedCssPropertyVecDestructor
97);
98impl_vec_partialeq!(ChangedCssProperty, ChangedCssPropertyVec);
99
100/// Focus state change for restyle operations
101#[derive(Debug, Clone, PartialEq)]
102pub struct FocusChange {
103    /// Node that lost focus (if any)
104    pub lost_focus: Option<NodeId>,
105    /// Node that gained focus (if any)
106    pub gained_focus: Option<NodeId>,
107}
108
109/// Hover state change for restyle operations
110#[derive(Debug, Clone, PartialEq)]
111pub struct HoverChange {
112    /// Nodes that the mouse left
113    pub left_nodes: Vec<NodeId>,
114    /// Nodes that the mouse entered
115    pub entered_nodes: Vec<NodeId>,
116}
117
118/// Active (mouse down) state change for restyle operations
119#[derive(Debug, Clone, PartialEq)]
120pub struct ActiveChange {
121    /// Nodes that were deactivated (mouse up)
122    pub deactivated: Vec<NodeId>,
123    /// Nodes that were activated (mouse down)
124    pub activated: Vec<NodeId>,
125}
126
127/// Result of a restyle operation, indicating what needs to be updated
128#[derive(Debug, Clone, Default)]
129pub struct RestyleResult {
130    /// Nodes whose CSS properties changed, with details of the changes
131    pub changed_nodes: BTreeMap<NodeId, Vec<ChangedCssProperty>>,
132    /// Whether layout needs to be recalculated (layout properties changed)
133    pub needs_layout: bool,
134    /// Whether display list needs regeneration (visual properties changed)
135    pub needs_display_list: bool,
136    /// Whether only GPU-level properties changed (opacity, transform)
137    /// If true and needs_display_list is false, we can update via GPU without display list rebuild
138    pub gpu_only_changes: bool,
139    /// The highest `RelayoutScope` seen across all property changes.
140    ///
141    /// This enables the IFC incremental layout optimization (Phase 2):
142    /// - `None`      → repaint only, zero layout work
143    /// - `IfcOnly`   → only the affected IFC needs re-shaping/repositioning
144    /// - `SizingOnly`→ this node's size changed, parent repositions siblings
145    /// - `Full`      → full subtree relayout
146    ///
147    /// When `max_relayout_scope <= IfcOnly`, the layout engine can skip
148    /// full `calculate_layout_for_subtree` and use the IFC fast path instead.
149    pub max_relayout_scope: RelayoutScope,
150}
151
152impl RestyleResult {
153    /// Returns true if any changes occurred
154    pub fn has_changes(&self) -> bool {
155        !self.changed_nodes.is_empty()
156    }
157
158    /// Merge another RestyleResult into this one
159    pub fn merge(&mut self, other: RestyleResult) {
160        for (node_id, changes) in other.changed_nodes {
161            self.changed_nodes.entry(node_id).or_default().extend(changes);
162        }
163        self.needs_layout = self.needs_layout || other.needs_layout;
164        self.needs_display_list = self.needs_display_list || other.needs_display_list;
165        self.gpu_only_changes = self.gpu_only_changes && other.gpu_only_changes;
166        // Keep the highest (most expensive) scope
167        if other.max_relayout_scope > self.max_relayout_scope {
168            self.max_relayout_scope = other.max_relayout_scope;
169        }
170    }
171}
172
173#[repr(C, u8)]
174#[derive(Debug, Clone, PartialEq, Hash, PartialOrd, Eq, Ord)]
175pub enum CssPropertySource {
176    Css(CssPath),
177    Inline,
178}
179
180/// NOTE: multiple states can be active at the same time
181///
182/// Tracks all CSS pseudo-class states for a node.
183/// Each flag is independent - a node can be both :hover and :focus simultaneously.
184#[repr(C)]
185#[derive(Clone, Copy, PartialEq, Hash, PartialOrd, Eq, Ord, Default)]
186pub struct StyledNodeState {
187    /// Element is being hovered (:hover)
188    pub hover: bool,
189    /// Element is active/being clicked (:active)
190    pub active: bool,
191    /// Element has focus (:focus)
192    pub focused: bool,
193    /// Element is disabled (:disabled)
194    pub disabled: bool,
195    /// Element is checked/selected (:checked)
196    pub checked: bool,
197    /// Element or descendant has focus (:focus-within)
198    pub focus_within: bool,
199    /// Link has been visited (:visited)
200    pub visited: bool,
201    /// Window is not focused (:backdrop) - GTK compatibility
202    pub backdrop: bool,
203    /// Element is currently being dragged (:dragging)
204    pub dragging: bool,
205    /// A dragged element is over this drop target (:drag-over)
206    pub drag_over: bool,
207}
208
209impl core::fmt::Debug for StyledNodeState {
210    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
211        let mut v = Vec::new();
212        if self.hover {
213            v.push("hover");
214        }
215        if self.active {
216            v.push("active");
217        }
218        if self.focused {
219            v.push("focused");
220        }
221        if self.disabled {
222            v.push("disabled");
223        }
224        if self.checked {
225            v.push("checked");
226        }
227        if self.focus_within {
228            v.push("focus_within");
229        }
230        if self.visited {
231            v.push("visited");
232        }
233        if self.backdrop {
234            v.push("backdrop");
235        }
236        if self.dragging {
237            v.push("dragging");
238        }
239        if self.drag_over {
240            v.push("drag_over");
241        }
242        if v.is_empty() {
243            v.push("normal");
244        }
245        write!(f, "{:?}", v)
246    }
247}
248
249impl StyledNodeState {
250    /// Creates a new state with all states set to false (normal state).
251    pub const fn new() -> Self {
252        StyledNodeState {
253            hover: false,
254            active: false,
255            focused: false,
256            disabled: false,
257            checked: false,
258            focus_within: false,
259            visited: false,
260            backdrop: false,
261            dragging: false,
262            drag_over: false,
263        }
264    }
265
266    /// Check if a specific pseudo-state is active
267    pub fn has_state(&self, state_type: u8) -> bool {
268        match state_type {
269            0 => true, // Normal is always active
270            1 => self.hover,
271            2 => self.active,
272            3 => self.focused,
273            4 => self.disabled,
274            5 => self.checked,
275            6 => self.focus_within,
276            7 => self.visited,
277            8 => self.backdrop,
278            9 => self.dragging,
279            10 => self.drag_over,
280            _ => false,
281        }
282    }
283
284    /// Returns true if no special state is active (just normal)
285    pub fn is_normal(&self) -> bool {
286        !self.hover
287            && !self.active
288            && !self.focused
289            && !self.disabled
290            && !self.checked
291            && !self.focus_within
292            && !self.visited
293            && !self.backdrop
294            && !self.dragging
295            && !self.drag_over
296    }
297
298    /// Convert to PseudoStateFlags for use with dynamic selectors
299    pub fn to_pseudo_state_flags(&self) -> azul_css::dynamic_selector::PseudoStateFlags {
300        azul_css::dynamic_selector::PseudoStateFlags {
301            hover: self.hover,
302            active: self.active,
303            focused: self.focused,
304            disabled: self.disabled,
305            checked: self.checked,
306            focus_within: self.focus_within,
307            visited: self.visited,
308            backdrop: self.backdrop,
309            dragging: self.dragging,
310            drag_over: self.drag_over,
311        }
312    }
313
314    /// Create from PseudoStateFlags (reverse of to_pseudo_state_flags)
315    pub fn from_pseudo_state_flags(flags: &azul_css::dynamic_selector::PseudoStateFlags) -> Self {
316        StyledNodeState {
317            hover: flags.hover,
318            active: flags.active,
319            focused: flags.focused,
320            disabled: flags.disabled,
321            checked: flags.checked,
322            focus_within: flags.focus_within,
323            visited: flags.visited,
324            backdrop: flags.backdrop,
325            dragging: flags.dragging,
326            drag_over: flags.drag_over,
327        }
328    }
329}
330
331/// A styled Dom node
332#[repr(C)]
333#[derive(Debug, Default, Clone, PartialEq, PartialOrd)]
334pub struct StyledNode {
335    /// Current state of this styled node (used later for caching the style / layout)
336    pub styled_node_state: StyledNodeState,
337}
338
339impl_option!(
340    StyledNode,
341    OptionStyledNode,
342    copy = false,
343    [Debug, Clone, PartialEq, PartialOrd]
344);
345
346impl_vec!(StyledNode, StyledNodeVec, StyledNodeVecDestructor, StyledNodeVecDestructorType, StyledNodeVecSlice, OptionStyledNode);
347impl_vec_mut!(StyledNode, StyledNodeVec);
348impl_vec_debug!(StyledNode, StyledNodeVec);
349impl_vec_partialord!(StyledNode, StyledNodeVec);
350impl_vec_clone!(StyledNode, StyledNodeVec, StyledNodeVecDestructor);
351impl_vec_partialeq!(StyledNode, StyledNodeVec);
352
353impl StyledNodeVec {
354    /// Returns an immutable container reference for indexed access.
355    pub fn as_container<'a>(&'a self) -> NodeDataContainerRef<'a, StyledNode> {
356        NodeDataContainerRef {
357            internal: self.as_ref(),
358        }
359    }
360    /// Returns a mutable container reference for indexed access.
361    pub fn as_container_mut<'a>(&'a mut self) -> NodeDataContainerRefMut<'a, StyledNode> {
362        NodeDataContainerRefMut {
363            internal: self.as_mut(),
364        }
365    }
366}
367
368#[test]
369fn test_it() {
370    let s = "
371        html, body, p {
372            margin: 0;
373            padding: 0;
374        }
375        #div1 {
376            border: solid black;
377            height: 2in;
378            position: absolute;
379            top: 1in;
380            width: 3in;
381        }
382        div div {
383            background: blue;
384            height: 1in;
385            position: fixed;
386            width: 1in;
387        }
388    ";
389
390    let css = azul_css::parser2::new_from_str(s);
391    let _styled_dom = Dom::create_body()
392        .with_children(
393            vec![Dom::create_div()
394                .with_ids_and_classes(
395                    vec![crate::dom::IdOrClass::Id("div1".to_string().into())].into(),
396                )
397                .with_children(vec![Dom::create_div()].into())]
398            .into(),
399        )
400        .style(css.0);
401}
402
403/// Calculated hash of a font-family
404#[derive(Copy, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)]
405pub struct StyleFontFamilyHash(pub u64);
406
407impl ::core::fmt::Debug for StyleFontFamilyHash {
408    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
409        write!(f, "StyleFontFamilyHash({})", self.0)
410    }
411}
412
413impl StyleFontFamilyHash {
414    /// Computes a 64-bit hash of a font family for cache lookups.
415    pub fn new(family: &StyleFontFamily) -> Self {
416        use highway::{HighwayHash, HighwayHasher, Key};
417        let mut hasher = HighwayHasher::new(Key([0; 4]));
418        family.hash(&mut hasher);
419        Self(hasher.finalize64())
420    }
421}
422
423/// Calculated hash of a font-family
424#[derive(Copy, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)]
425pub struct StyleFontFamiliesHash(pub u64);
426
427impl ::core::fmt::Debug for StyleFontFamiliesHash {
428    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
429        write!(f, "StyleFontFamiliesHash({})", self.0)
430    }
431}
432
433impl StyleFontFamiliesHash {
434    /// Computes a 64-bit hash of multiple font families for cache lookups.
435    pub fn new(families: &[StyleFontFamily]) -> Self {
436        use highway::{HighwayHash, HighwayHasher, Key};
437        let mut hasher = HighwayHasher::new(Key([0; 4]));
438        for f in families.iter() {
439            f.hash(&mut hasher);
440        }
441        Self(hasher.finalize64())
442    }
443}
444
445/// FFI-safe representation of `Option<NodeId>` as a single `usize`.
446///
447/// # Encoding (1-based)
448///
449/// - `inner = 0` → `None` (no node)
450/// - `inner = n > 0` → `Some(NodeId(n - 1))`
451///
452/// This type exists because C/C++ cannot use Rust's `Option` type.
453/// Use [`NodeHierarchyItemId::into_crate_internal`] to decode and
454/// [`NodeHierarchyItemId::from_crate_internal`] to encode.
455///
456/// # Difference from `NodeId`
457///
458/// - **`NodeId`**: A 0-based array index. `NodeId::new(0)` refers to the first node.
459///   Use directly for array indexing: `nodes[node_id.index()]`.
460///
461/// - **`NodeHierarchyItemId`**: A 1-based encoded `Option<NodeId>`.
462///   `inner = 0` means `None`, `inner = 1` means `Some(NodeId(0))`.
463///   **Never use `inner` as an array index!** Always decode first.
464///
465/// # Warning
466///
467/// The `inner` field uses **1-based encoding**, not a direct index!
468/// Never use `inner` directly as an array index - always decode first.
469///
470/// # Example
471///
472/// ```ignore
473/// // Encoding: Option<NodeId> -> NodeHierarchyItemId
474/// let opt = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(5)));
475/// assert_eq!(opt.into_raw(), 6);  // 5 + 1 = 6
476///
477/// // Decoding: NodeHierarchyItemId -> Option<NodeId>
478/// let decoded = opt.into_crate_internal();
479/// assert_eq!(decoded, Some(NodeId::new(5)));
480///
481/// // None case
482/// let none = NodeHierarchyItemId::NONE;
483/// assert_eq!(none.into_raw(), 0);
484/// assert_eq!(none.into_crate_internal(), None);
485/// ```
486#[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
487#[repr(C)]
488pub struct NodeHierarchyItemId {
489    // Uses 1-based encoding: 0 = None, n > 0 = Some(NodeId(n-1))
490    // Do NOT use directly as an array index!
491    inner: usize,
492}
493
494impl fmt::Debug for NodeHierarchyItemId {
495    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
496        match self.into_crate_internal() {
497            Some(n) => write!(f, "Some(NodeId({}))", n),
498            None => write!(f, "None"),
499        }
500    }
501}
502
503impl fmt::Display for NodeHierarchyItemId {
504    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
505        write!(f, "{:?}", self)
506    }
507}
508
509impl NodeHierarchyItemId {
510    /// Represents `None` (no node). Encoded as `inner = 0`.
511    pub const NONE: NodeHierarchyItemId = NodeHierarchyItemId { inner: 0 };
512
513    /// Creates an `NodeHierarchyItemId` from a raw 1-based encoded value.
514    ///
515    /// # Warning
516    ///
517    /// The value must use 1-based encoding (0 = None, n = NodeId(n-1)).
518    /// Prefer using [`NodeHierarchyItemId::from_crate_internal`] instead.
519    #[inline]
520    pub const fn from_raw(value: usize) -> Self {
521        Self { inner: value }
522    }
523
524    /// Returns the raw 1-based encoded value.
525    ///
526    /// # Warning
527    ///
528    /// The returned value uses 1-based encoding. Do NOT use as an array index!
529    #[inline]
530    pub const fn into_raw(&self) -> usize {
531        self.inner
532    }
533}
534
535impl_option!(
536    NodeHierarchyItemId,
537    OptionNodeHierarchyItemId,
538    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
539);
540
541impl_vec!(NodeHierarchyItemId, NodeHierarchyItemIdVec, NodeHierarchyItemIdVecDestructor, NodeHierarchyItemIdVecDestructorType, NodeHierarchyItemIdVecSlice, OptionNodeHierarchyItemId);
542impl_vec_mut!(NodeHierarchyItemId, NodeHierarchyItemIdVec);
543impl_vec_debug!(NodeHierarchyItemId, NodeHierarchyItemIdVec);
544impl_vec_ord!(NodeHierarchyItemId, NodeHierarchyItemIdVec);
545impl_vec_eq!(NodeHierarchyItemId, NodeHierarchyItemIdVec);
546impl_vec_hash!(NodeHierarchyItemId, NodeHierarchyItemIdVec);
547impl_vec_partialord!(NodeHierarchyItemId, NodeHierarchyItemIdVec);
548impl_vec_clone!(NodeHierarchyItemId, NodeHierarchyItemIdVec, NodeHierarchyItemIdVecDestructor);
549impl_vec_partialeq!(NodeHierarchyItemId, NodeHierarchyItemIdVec);
550
551impl NodeHierarchyItemId {
552    /// Decodes to `Option<NodeId>` (0 = None, n > 0 = Some(NodeId(n-1))).
553    #[inline]
554    pub const fn into_crate_internal(&self) -> Option<NodeId> {
555        NodeId::from_usize(self.inner)
556    }
557
558    /// Encodes from `Option<NodeId>` (None → 0, Some(NodeId(n)) → n+1).
559    #[inline]
560    pub const fn from_crate_internal(t: Option<NodeId>) -> Self {
561        Self {
562            inner: NodeId::into_raw(&t),
563        }
564    }
565}
566
567impl From<Option<NodeId>> for NodeHierarchyItemId {
568    #[inline]
569    fn from(opt: Option<NodeId>) -> Self {
570        NodeHierarchyItemId::from_crate_internal(opt)
571    }
572}
573
574impl From<NodeHierarchyItemId> for Option<NodeId> {
575    #[inline]
576    fn from(id: NodeHierarchyItemId) -> Self {
577        id.into_crate_internal()
578    }
579}
580
581#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
582#[repr(C)]
583pub struct NodeHierarchyItem {
584    pub parent: usize,
585    pub previous_sibling: usize,
586    pub next_sibling: usize,
587    pub last_child: usize,
588}
589
590impl_option!(
591    NodeHierarchyItem,
592    OptionNodeHierarchyItem,
593    [Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash]
594);
595
596impl NodeHierarchyItem {
597    /// Creates a zeroed hierarchy item (no parent, siblings, or children).
598    pub const fn zeroed() -> Self {
599        Self {
600            parent: 0,
601            previous_sibling: 0,
602            next_sibling: 0,
603            last_child: 0,
604        }
605    }
606}
607
608impl From<Node> for NodeHierarchyItem {
609    fn from(node: Node) -> NodeHierarchyItem {
610        NodeHierarchyItem {
611            parent: NodeId::into_raw(&node.parent),
612            previous_sibling: NodeId::into_raw(&node.previous_sibling),
613            next_sibling: NodeId::into_raw(&node.next_sibling),
614            last_child: NodeId::into_raw(&node.last_child),
615        }
616    }
617}
618
619impl NodeHierarchyItem {
620    /// Returns the parent node ID, if any.
621    pub fn parent_id(&self) -> Option<NodeId> {
622        NodeId::from_usize(self.parent)
623    }
624    /// Returns the previous sibling node ID, if any.
625    pub fn previous_sibling_id(&self) -> Option<NodeId> {
626        NodeId::from_usize(self.previous_sibling)
627    }
628    /// Returns the next sibling node ID, if any.
629    pub fn next_sibling_id(&self) -> Option<NodeId> {
630        NodeId::from_usize(self.next_sibling)
631    }
632    /// Returns the first child node ID (current_node_id + 1 if has children).
633    pub fn first_child_id(&self, current_node_id: NodeId) -> Option<NodeId> {
634        self.last_child_id().map(|_| current_node_id + 1)
635    }
636    /// Returns the last child node ID, if any.
637    pub fn last_child_id(&self) -> Option<NodeId> {
638        NodeId::from_usize(self.last_child)
639    }
640}
641
642impl_vec!(NodeHierarchyItem, NodeHierarchyItemVec, NodeHierarchyItemVecDestructor, NodeHierarchyItemVecDestructorType, NodeHierarchyItemVecSlice, OptionNodeHierarchyItem);
643impl_vec_mut!(NodeHierarchyItem, NodeHierarchyItemVec);
644impl_vec_debug!(AzNode, NodeHierarchyItemVec);
645impl_vec_partialord!(AzNode, NodeHierarchyItemVec);
646impl_vec_clone!(
647    NodeHierarchyItem,
648    NodeHierarchyItemVec,
649    NodeHierarchyItemVecDestructor
650);
651impl_vec_partialeq!(AzNode, NodeHierarchyItemVec);
652
653impl NodeHierarchyItemVec {
654    /// Returns an immutable container reference for indexed access.
655    pub fn as_container<'a>(&'a self) -> NodeDataContainerRef<'a, NodeHierarchyItem> {
656        NodeDataContainerRef {
657            internal: self.as_ref(),
658        }
659    }
660    /// Returns a mutable container reference for indexed access.
661    pub fn as_container_mut<'a>(&'a mut self) -> NodeDataContainerRefMut<'a, NodeHierarchyItem> {
662        NodeDataContainerRefMut {
663            internal: self.as_mut(),
664        }
665    }
666}
667
668impl<'a> NodeDataContainerRef<'a, NodeHierarchyItem> {
669    /// Returns the number of descendant nodes under the given parent.
670    #[inline]
671    pub fn subtree_len(&self, parent_id: NodeId) -> usize {
672        let self_item_index = parent_id.index();
673        let next_item_index = match self[parent_id].next_sibling_id() {
674            None => self.len(),
675            Some(s) => s.index(),
676        };
677        next_item_index - self_item_index - 1
678    }
679}
680
681#[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
682#[repr(C)]
683pub struct ParentWithNodeDepth {
684    pub depth: usize,
685    pub node_id: NodeHierarchyItemId,
686}
687
688impl_option!(
689    ParentWithNodeDepth,
690    OptionParentWithNodeDepth,
691    [Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash]
692);
693
694impl core::fmt::Debug for ParentWithNodeDepth {
695    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
696        write!(
697            f,
698            "{{ depth: {}, node: {:?} }}",
699            self.depth,
700            self.node_id.into_crate_internal()
701        )
702    }
703}
704
705impl_vec!(ParentWithNodeDepth, ParentWithNodeDepthVec, ParentWithNodeDepthVecDestructor, ParentWithNodeDepthVecDestructorType, ParentWithNodeDepthVecSlice, OptionParentWithNodeDepth);
706impl_vec_mut!(ParentWithNodeDepth, ParentWithNodeDepthVec);
707impl_vec_debug!(ParentWithNodeDepth, ParentWithNodeDepthVec);
708impl_vec_partialord!(ParentWithNodeDepth, ParentWithNodeDepthVec);
709impl_vec_clone!(
710    ParentWithNodeDepth,
711    ParentWithNodeDepthVec,
712    ParentWithNodeDepthVecDestructor
713);
714impl_vec_partialeq!(ParentWithNodeDepth, ParentWithNodeDepthVec);
715
716#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd)]
717#[repr(C)]
718pub struct TagIdToNodeIdMapping {
719    // Hit-testing tag ID (not all nodes have a tag, only nodes that are hit-testable)
720    pub tag_id: TagId,
721    /// Node ID of the node that has a tag
722    pub node_id: NodeHierarchyItemId,
723    /// Whether this node has a tab-index field
724    pub tab_index: OptionTabIndex,
725}
726
727impl_option!(
728    TagIdToNodeIdMapping,
729    OptionTagIdToNodeIdMapping,
730    copy = false,
731    [Debug, Clone, PartialEq, Eq, Ord, PartialOrd]
732);
733
734impl_vec!(TagIdToNodeIdMapping, TagIdToNodeIdMappingVec, TagIdToNodeIdMappingVecDestructor, TagIdToNodeIdMappingVecDestructorType, TagIdToNodeIdMappingVecSlice, OptionTagIdToNodeIdMapping);
735impl_vec_mut!(TagIdToNodeIdMapping, TagIdToNodeIdMappingVec);
736impl_vec_debug!(TagIdToNodeIdMapping, TagIdToNodeIdMappingVec);
737impl_vec_partialord!(TagIdToNodeIdMapping, TagIdToNodeIdMappingVec);
738impl_vec_clone!(
739    TagIdToNodeIdMapping,
740    TagIdToNodeIdMappingVec,
741    TagIdToNodeIdMappingVecDestructor
742);
743impl_vec_partialeq!(TagIdToNodeIdMapping, TagIdToNodeIdMappingVec);
744
745#[derive(Debug, Clone, PartialEq, PartialOrd)]
746#[repr(C)]
747pub struct ContentGroup {
748    /// The parent of the current node group, i.e. either the root node (0)
749    /// or the last positioned node ()
750    pub root: NodeHierarchyItemId,
751    /// Node ids in order of drawing
752    pub children: ContentGroupVec,
753}
754
755impl_option!(
756    ContentGroup,
757    OptionContentGroup,
758    copy = false,
759    [Debug, Clone, PartialEq, PartialOrd]
760);
761
762impl_vec!(ContentGroup, ContentGroupVec, ContentGroupVecDestructor, ContentGroupVecDestructorType, ContentGroupVecSlice, OptionContentGroup);
763impl_vec_mut!(ContentGroup, ContentGroupVec);
764impl_vec_debug!(ContentGroup, ContentGroupVec);
765impl_vec_partialord!(ContentGroup, ContentGroupVec);
766impl_vec_clone!(ContentGroup, ContentGroupVec, ContentGroupVecDestructor);
767impl_vec_partialeq!(ContentGroup, ContentGroupVec);
768
769#[derive(Debug, PartialEq, Clone)]
770#[repr(C)]
771pub struct StyledDom {
772    pub root: NodeHierarchyItemId,
773    pub node_hierarchy: NodeHierarchyItemVec,
774    pub node_data: NodeDataVec,
775    pub styled_nodes: StyledNodeVec,
776    pub cascade_info: CascadeInfoVec,
777    pub nodes_with_window_callbacks: NodeHierarchyItemIdVec,
778    pub nodes_with_not_callbacks: NodeHierarchyItemIdVec,
779    pub nodes_with_datasets: NodeHierarchyItemIdVec,
780    pub tag_ids_to_node_ids: TagIdToNodeIdMappingVec,
781    pub non_leaf_nodes: ParentWithNodeDepthVec,
782    pub css_property_cache: CssPropertyCachePtr,
783    /// The ID of this DOM in the layout tree (for multi-DOM support with IFrames)
784    pub dom_id: DomId,
785}
786impl_option!(
787    StyledDom,
788    OptionStyledDom,
789    copy = false,
790    [Debug, Clone, PartialEq]
791);
792
793impl Default for StyledDom {
794    fn default() -> Self {
795        let root_node: NodeHierarchyItem = Node::ROOT.into();
796        let root_node_id: NodeHierarchyItemId =
797            NodeHierarchyItemId::from_crate_internal(Some(NodeId::ZERO));
798        Self {
799            root: root_node_id,
800            node_hierarchy: vec![root_node].into(),
801            node_data: vec![NodeData::create_body()].into(),
802            styled_nodes: vec![StyledNode::default()].into(),
803            cascade_info: vec![CascadeInfo {
804                index_in_parent: 0,
805                is_last_child: true,
806            }]
807            .into(),
808            tag_ids_to_node_ids: Vec::new().into(),
809            non_leaf_nodes: vec![ParentWithNodeDepth {
810                depth: 0,
811                node_id: root_node_id,
812            }]
813            .into(),
814            nodes_with_window_callbacks: Vec::new().into(),
815            nodes_with_not_callbacks: Vec::new().into(),
816            nodes_with_datasets: Vec::new().into(),
817            css_property_cache: CssPropertyCachePtr::new(CssPropertyCache::empty(1)),
818            dom_id: DomId::ROOT_ID,
819        }
820    }
821}
822
823impl StyledDom {
824    /// Creates a new StyledDom by applying CSS styles to a DOM tree.
825    ///
826    /// NOTE: After calling this function, the DOM will be reset to an empty DOM.
827    // This is for memory optimization, so that the DOM does not need to be cloned.
828    //
829    // The CSS will be left in-place, but will be re-ordered
830    pub fn create(dom: &mut Dom, mut css: Css) -> Self {
831        use core::mem;
832
833        use crate::dom::EventFilter;
834
835        let mut swap_dom = Dom::create_body();
836
837        mem::swap(dom, &mut swap_dom);
838
839        let compact_dom: CompactDom = swap_dom.into();
840        let non_leaf_nodes = compact_dom
841            .node_hierarchy
842            .as_ref()
843            .get_parents_sorted_by_depth();
844        let node_hierarchy: NodeHierarchyItemVec = compact_dom
845            .node_hierarchy
846            .as_ref()
847            .internal
848            .iter()
849            .map(|i| (*i).into())
850            .collect::<Vec<NodeHierarchyItem>>()
851            .into();
852
853        let mut styled_nodes = vec![
854            StyledNode {
855                styled_node_state: StyledNodeState::new()
856            };
857            compact_dom.len()
858        ];
859
860        // fill out the css property cache: compute the inline properties first so that
861        // we can early-return in case the css is empty
862
863        let mut css_property_cache = CssPropertyCache::empty(compact_dom.node_data.len());
864
865        let html_tree =
866            construct_html_cascade_tree(
867                &compact_dom.node_hierarchy.as_ref(),
868                &non_leaf_nodes[..],
869                &compact_dom.node_data.as_ref(),
870            );
871
872        let non_leaf_nodes = non_leaf_nodes
873            .iter()
874            .map(|(depth, node_id)| ParentWithNodeDepth {
875                depth: *depth,
876                node_id: NodeHierarchyItemId::from_crate_internal(Some(*node_id)),
877            })
878            .collect::<Vec<_>>();
879
880        let non_leaf_nodes: ParentWithNodeDepthVec = non_leaf_nodes.into();
881
882        // apply all the styles from the CSS
883        let tag_ids = css_property_cache.restyle(
884            &mut css,
885            &compact_dom.node_data.as_ref(),
886            &node_hierarchy,
887            &non_leaf_nodes,
888            &html_tree.as_ref(),
889        );
890
891        // Apply UA CSS properties to all nodes (lowest priority in cascade)
892        // This MUST be done before compute_inherited_values() so that UA CSS
893        // properties can be inherited by child nodes (especially text nodes)
894        css_property_cache.apply_ua_css(compact_dom.node_data.as_ref().internal);
895
896        // Compute inherited values for all nodes (resolves em, %, etc.)
897        // This must be called after restyle() and apply_ua_css() to ensure
898        // CSS properties are available for inheritance
899        css_property_cache.compute_inherited_values(
900            node_hierarchy.as_container().internal,
901            compact_dom.node_data.as_ref().internal,
902        );
903
904        // Build pre-resolved property cache for O(1) layout lookups.
905        // Must be called after restyle() + apply_ua_css() + compute_inherited_values()
906        // so that all cascade sources are populated.
907        // NOTE: Currently disabled — the build cost (~70ms for 12K nodes) roughly equals
908        // the layout savings (~20ms), so net benefit is negligible. The Vec-based source
909        // maps already give O(1) outer lookup. Uncomment if build cost is reduced further.
910        // css_property_cache.resolved_cache = css_property_cache.build_resolved_cache(
911        //     compact_dom.node_data.as_ref().internal,
912        //     &styled_nodes,
913        // );
914
915        // Build compact layout cache: three-tier numeric encoding for O(1) layout lookups.
916        // Resolves all layout-relevant properties for the "normal" state and encodes them
917        // into cache-friendly arrays (8+72+24+8 = 112 bytes/node).
918        let compact = css_property_cache.build_compact_cache(
919            compact_dom.node_data.as_ref().internal,
920        );
921        css_property_cache.compact_cache = Some(compact);
922
923        // Pre-filter all EventFilter::Window and EventFilter::Not nodes
924        // since we need them in the CallbacksOfHitTest::new function
925        let nodes_with_window_callbacks = compact_dom
926            .node_data
927            .as_ref()
928            .internal
929            .iter()
930            .enumerate()
931            .filter_map(|(node_id, c)| {
932                let node_has_none_callbacks = c.get_callbacks().iter().any(|cb| match cb.event {
933                    EventFilter::Window(_) => true,
934                    _ => false,
935                });
936                if node_has_none_callbacks {
937                    Some(NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(
938                        node_id,
939                    ))))
940                } else {
941                    None
942                }
943            })
944            .collect::<Vec<_>>();
945
946        let nodes_with_not_callbacks = compact_dom
947            .node_data
948            .as_ref()
949            .internal
950            .iter()
951            .enumerate()
952            .filter_map(|(node_id, c)| {
953                let node_has_none_callbacks = c.get_callbacks().iter().any(|cb| match cb.event {
954                    EventFilter::Not(_) => true,
955                    _ => false,
956                });
957                if node_has_none_callbacks {
958                    Some(NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(
959                        node_id,
960                    ))))
961                } else {
962                    None
963                }
964            })
965            .collect::<Vec<_>>();
966
967        // collect nodes with either dataset or callback properties
968        let nodes_with_datasets = compact_dom
969            .node_data
970            .as_ref()
971            .internal
972            .iter()
973            .enumerate()
974            .filter_map(|(node_id, c)| {
975                if !c.get_callbacks().is_empty() || c.get_dataset().is_some() {
976                    Some(NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(
977                        node_id,
978                    ))))
979                } else {
980                    None
981                }
982            })
983            .collect::<Vec<_>>();
984
985        let mut styled_dom = StyledDom {
986            root: NodeHierarchyItemId::from_crate_internal(Some(compact_dom.root)),
987            node_hierarchy,
988            node_data: compact_dom.node_data.internal.into(),
989            cascade_info: html_tree.internal.into(),
990            styled_nodes: styled_nodes.into(),
991            tag_ids_to_node_ids: tag_ids.into(),
992            nodes_with_window_callbacks: nodes_with_window_callbacks.into(),
993            nodes_with_not_callbacks: nodes_with_not_callbacks.into(),
994            nodes_with_datasets: nodes_with_datasets.into(),
995            non_leaf_nodes,
996            css_property_cache: CssPropertyCachePtr::new(css_property_cache),
997            dom_id: DomId::ROOT_ID, // Will be assigned by layout engine for iframes
998        };
999
1000        // Generate anonymous table elements if needed (CSS 2.2 Section 17.2.1)
1001        // This must happen after CSS cascade but before layout
1002        // Anonymous nodes are marked with is_anonymous=true and are skipped by CallbackInfo
1003        #[cfg(feature = "table_layout")]
1004        if let Err(e) = crate::dom_table::generate_anonymous_table_elements(&mut styled_dom) {
1005            eprintln!(
1006                "Warning: Failed to generate anonymous table elements: {:?}",
1007                e
1008            );
1009        }
1010
1011        styled_dom
1012    }
1013
1014    /// Appends another `StyledDom` as a child to the `self.root`
1015    /// without re-styling the DOM itself
1016    pub fn append_child(&mut self, mut other: Self) {
1017        // shift all the node ids in other by self.len()
1018        let self_len = self.node_hierarchy.as_ref().len();
1019        let other_len = other.node_hierarchy.as_ref().len();
1020        let self_root_id = self.root.into_crate_internal().unwrap_or(NodeId::ZERO);
1021        let other_root_id = other.root.into_crate_internal().unwrap_or(NodeId::ZERO);
1022
1023        // iterate through the direct root children and adjust the cascade_info
1024        let current_root_children_count = self_root_id
1025            .az_children(&self.node_hierarchy.as_container())
1026            .count();
1027
1028        other.cascade_info.as_mut()[other_root_id.index()].index_in_parent =
1029            current_root_children_count as u32;
1030        other.cascade_info.as_mut()[other_root_id.index()].is_last_child = true;
1031
1032        self.cascade_info.append(&mut other.cascade_info);
1033
1034        // adjust node hierarchy
1035        // Note: 0 means "no node" (None) in the 1-based encoding used by from_usize/into_usize
1036        for other in other.node_hierarchy.as_mut().iter_mut() {
1037            if other.parent != 0 {
1038                other.parent += self_len;
1039            }
1040            if other.previous_sibling != 0 {
1041                other.previous_sibling += self_len;
1042            }
1043            if other.next_sibling != 0 {
1044                other.next_sibling += self_len;
1045            }
1046            if other.last_child != 0 {
1047                other.last_child += self_len;
1048            }
1049        }
1050
1051        other.node_hierarchy.as_container_mut()[other_root_id].parent =
1052            NodeId::into_raw(&Some(self_root_id));
1053        let current_last_child = self.node_hierarchy.as_container()[self_root_id].last_child_id();
1054        other.node_hierarchy.as_container_mut()[other_root_id].previous_sibling =
1055            NodeId::into_raw(&current_last_child);
1056        if let Some(current_last) = current_last_child {
1057            if self.node_hierarchy.as_container_mut()[current_last]
1058                .next_sibling_id()
1059                .is_some()
1060            {
1061                self.node_hierarchy.as_container_mut()[current_last].next_sibling +=
1062                    other_root_id.index() + other_len;
1063            } else {
1064                self.node_hierarchy.as_container_mut()[current_last].next_sibling =
1065                    NodeId::into_raw(&Some(NodeId::new(self_len + other_root_id.index())));
1066            }
1067        }
1068        self.node_hierarchy.as_container_mut()[self_root_id].last_child =
1069            NodeId::into_raw(&Some(NodeId::new(self_len + other_root_id.index())));
1070
1071        self.node_hierarchy.append(&mut other.node_hierarchy);
1072        self.node_data.append(&mut other.node_data);
1073        self.styled_nodes.append(&mut other.styled_nodes);
1074        self.get_css_property_cache_mut()
1075            .append(other.get_css_property_cache_mut());
1076
1077        // Tag IDs are globally unique (AtomicUsize counter) and never collide,
1078        // so we only shift node_id (which changes when DOMs are merged).
1079        for tag_id_node_id in other.tag_ids_to_node_ids.iter_mut() {
1080            tag_id_node_id.node_id.inner += self_len;
1081        }
1082
1083        self.tag_ids_to_node_ids
1084            .append(&mut other.tag_ids_to_node_ids);
1085
1086        for nid in other.nodes_with_window_callbacks.iter_mut() {
1087            nid.inner += self_len;
1088        }
1089        self.nodes_with_window_callbacks
1090            .append(&mut other.nodes_with_window_callbacks);
1091
1092        for nid in other.nodes_with_not_callbacks.iter_mut() {
1093            nid.inner += self_len;
1094        }
1095        self.nodes_with_not_callbacks
1096            .append(&mut other.nodes_with_not_callbacks);
1097
1098        for nid in other.nodes_with_datasets.iter_mut() {
1099            nid.inner += self_len;
1100        }
1101        self.nodes_with_datasets
1102            .append(&mut other.nodes_with_datasets);
1103
1104        // edge case: if the other StyledDom consists of only one node
1105        // then it is not a parent itself
1106        if other_len != 1 {
1107            for other_non_leaf_node in other.non_leaf_nodes.iter_mut() {
1108                other_non_leaf_node.node_id.inner += self_len;
1109                other_non_leaf_node.depth += 1;
1110            }
1111            self.non_leaf_nodes.append(&mut other.non_leaf_nodes);
1112            self.non_leaf_nodes.sort_by(|a, b| a.depth.cmp(&b.depth));
1113        }
1114    }
1115
1116    /// Optimized version of `append_child` that takes the child index directly
1117    /// instead of counting existing children (O(1) instead of O(n))
1118    pub fn append_child_with_index(&mut self, mut other: Self, child_index: usize) {
1119        // shift all the node ids in other by self.len()
1120        let self_len = self.node_hierarchy.as_ref().len();
1121        let other_len = other.node_hierarchy.as_ref().len();
1122        let self_root_id = self.root.into_crate_internal().unwrap_or(NodeId::ZERO);
1123        let other_root_id = other.root.into_crate_internal().unwrap_or(NodeId::ZERO);
1124
1125        // Use provided index instead of counting children
1126        other.cascade_info.as_mut()[other_root_id.index()].index_in_parent = child_index as u32;
1127        other.cascade_info.as_mut()[other_root_id.index()].is_last_child = true;
1128
1129        self.cascade_info.append(&mut other.cascade_info);
1130
1131        // adjust node hierarchy
1132        for other in other.node_hierarchy.as_mut().iter_mut() {
1133            if other.parent != 0 {
1134                other.parent += self_len;
1135            }
1136            if other.previous_sibling != 0 {
1137                other.previous_sibling += self_len;
1138            }
1139            if other.next_sibling != 0 {
1140                other.next_sibling += self_len;
1141            }
1142            if other.last_child != 0 {
1143                other.last_child += self_len;
1144            }
1145        }
1146
1147        other.node_hierarchy.as_container_mut()[other_root_id].parent =
1148            NodeId::into_raw(&Some(self_root_id));
1149        let current_last_child = self.node_hierarchy.as_container()[self_root_id].last_child_id();
1150        other.node_hierarchy.as_container_mut()[other_root_id].previous_sibling =
1151            NodeId::into_raw(&current_last_child);
1152        if let Some(current_last) = current_last_child {
1153            if self.node_hierarchy.as_container_mut()[current_last]
1154                .next_sibling_id()
1155                .is_some()
1156            {
1157                self.node_hierarchy.as_container_mut()[current_last].next_sibling +=
1158                    other_root_id.index() + other_len;
1159            } else {
1160                self.node_hierarchy.as_container_mut()[current_last].next_sibling =
1161                    NodeId::into_raw(&Some(NodeId::new(self_len + other_root_id.index())));
1162            }
1163        }
1164        self.node_hierarchy.as_container_mut()[self_root_id].last_child =
1165            NodeId::into_raw(&Some(NodeId::new(self_len + other_root_id.index())));
1166
1167        self.node_hierarchy.append(&mut other.node_hierarchy);
1168        self.node_data.append(&mut other.node_data);
1169        self.styled_nodes.append(&mut other.styled_nodes);
1170        self.get_css_property_cache_mut()
1171            .append(other.get_css_property_cache_mut());
1172
1173        // Tag IDs are globally unique (AtomicUsize counter) and never collide,
1174        // so we only shift node_id (which changes when DOMs are merged).
1175        for tag_id_node_id in other.tag_ids_to_node_ids.iter_mut() {
1176            tag_id_node_id.node_id.inner += self_len;
1177        }
1178
1179        self.tag_ids_to_node_ids
1180            .append(&mut other.tag_ids_to_node_ids);
1181
1182        for nid in other.nodes_with_window_callbacks.iter_mut() {
1183            nid.inner += self_len;
1184        }
1185        self.nodes_with_window_callbacks
1186            .append(&mut other.nodes_with_window_callbacks);
1187
1188        for nid in other.nodes_with_not_callbacks.iter_mut() {
1189            nid.inner += self_len;
1190        }
1191        self.nodes_with_not_callbacks
1192            .append(&mut other.nodes_with_not_callbacks);
1193
1194        for nid in other.nodes_with_datasets.iter_mut() {
1195            nid.inner += self_len;
1196        }
1197        self.nodes_with_datasets
1198            .append(&mut other.nodes_with_datasets);
1199
1200        // edge case: if the other StyledDom consists of only one node
1201        // then it is not a parent itself
1202        if other_len != 1 {
1203            for other_non_leaf_node in other.non_leaf_nodes.iter_mut() {
1204                other_non_leaf_node.node_id.inner += self_len;
1205                other_non_leaf_node.depth += 1;
1206            }
1207            self.non_leaf_nodes.append(&mut other.non_leaf_nodes);
1208            // NOTE: Sorting deferred - call finalize_non_leaf_nodes() after all appends
1209        }
1210    }
1211
1212    /// Call this after all append_child_with_index operations are complete
1213    /// to sort non_leaf_nodes by depth (required for correct rendering)
1214    pub fn finalize_non_leaf_nodes(&mut self) {
1215        self.non_leaf_nodes.sort_by(|a, b| a.depth.cmp(&b.depth));
1216    }
1217
1218    /// Same as `append_child()`, but as a builder method
1219    pub fn with_child(mut self, other: Self) -> Self {
1220        self.append_child(other);
1221        self
1222    }
1223
1224    /// Sets the context menu for the root node
1225    pub fn set_context_menu(&mut self, context_menu: Menu) {
1226        if let Some(root_id) = self.root.into_crate_internal() {
1227            self.node_data.as_container_mut()[root_id].set_context_menu(context_menu);
1228        }
1229    }
1230
1231    /// Builder method for setting the context menu
1232    pub fn with_context_menu(mut self, context_menu: Menu) -> Self {
1233        self.set_context_menu(context_menu);
1234        self
1235    }
1236
1237    /// Sets the menu bar for the root node
1238    pub fn set_menu_bar(&mut self, menu_bar: Menu) {
1239        if let Some(root_id) = self.root.into_crate_internal() {
1240            self.node_data.as_container_mut()[root_id].set_menu_bar(menu_bar);
1241        }
1242    }
1243
1244    /// Builder method for setting the menu bar
1245    pub fn with_menu_bar(mut self, menu_bar: Menu) -> Self {
1246        self.set_menu_bar(menu_bar);
1247        self
1248    }
1249
1250    /// Re-applies CSS styles to the existing DOM structure.
1251    pub fn restyle(&mut self, mut css: Css) {
1252        let new_tag_ids = self.css_property_cache.downcast_mut().restyle(
1253            &mut css,
1254            &self.node_data.as_container(),
1255            &self.node_hierarchy,
1256            &self.non_leaf_nodes,
1257            &self.cascade_info.as_container(),
1258        );
1259
1260        // Apply UA CSS properties before computing inheritance
1261        self.css_property_cache
1262            .downcast_mut()
1263            .apply_ua_css(self.node_data.as_container().internal);
1264
1265        // Compute inherited values after restyle and apply_ua_css (resolves em, %, etc.)
1266        self.css_property_cache
1267            .downcast_mut()
1268            .compute_inherited_values(
1269                self.node_hierarchy.as_container().internal,
1270                self.node_data.as_container().internal,
1271            );
1272
1273        self.tag_ids_to_node_ids = new_tag_ids.into();
1274    }
1275
1276    /// Returns the total number of nodes in this StyledDom.
1277    #[inline]
1278    pub fn node_count(&self) -> usize {
1279        self.node_data.len()
1280    }
1281
1282    /// Returns an immutable reference to the CSS property cache.
1283    #[inline]
1284    pub fn get_css_property_cache<'a>(&'a self) -> &'a CssPropertyCache {
1285        &*self.css_property_cache.ptr
1286    }
1287
1288    /// Returns a mutable reference to the CSS property cache.
1289    #[inline]
1290    pub fn get_css_property_cache_mut<'a>(&'a mut self) -> &'a mut CssPropertyCache {
1291        &mut *self.css_property_cache.ptr
1292    }
1293
1294    /// Returns the current state (hover, active, focus) of a styled node.
1295    #[inline]
1296    pub fn get_styled_node_state(&self, node_id: &NodeId) -> StyledNodeState {
1297        self.styled_nodes.as_container()[*node_id]
1298            .styled_node_state
1299            .clone()
1300    }
1301
1302    /// Scans the display list for all image keys
1303    pub fn scan_for_image_keys(&self, css_image_cache: &ImageCache) -> FastBTreeSet<ImageRef> {
1304        use azul_css::props::style::StyleBackgroundContentVec;
1305
1306        use crate::{dom::NodeType::*, resources::OptionImageMask};
1307
1308        #[derive(Default)]
1309        struct ScanImageVec {
1310            node_type_image: Option<ImageRef>,
1311            background_image: Vec<ImageRef>,
1312            clip_mask: Option<ImageRef>,
1313        }
1314
1315        let default_backgrounds: StyleBackgroundContentVec = Vec::new().into();
1316
1317        let images = self
1318            .node_data
1319            .as_container()
1320            .internal
1321            .iter()
1322            .enumerate()
1323            .map(|(node_id, node_data)| {
1324                let node_id = NodeId::new(node_id);
1325                let mut v = ScanImageVec::default();
1326
1327                // If the node has an image content, it needs to be uploaded
1328                if let Image(id) = node_data.get_node_type() {
1329                    v.node_type_image = Some(id.clone());
1330                }
1331
1332                // If the node has a CSS background image, it needs to be uploaded
1333                let opt_background_image = self.get_css_property_cache().get_background_content(
1334                    &node_data,
1335                    &node_id,
1336                    &self.styled_nodes.as_container()[node_id].styled_node_state,
1337                );
1338
1339                if let Some(style_backgrounds) = opt_background_image {
1340                    v.background_image = style_backgrounds
1341                        .get_property()
1342                        .unwrap_or(&default_backgrounds)
1343                        .iter()
1344                        .filter_map(|bg| {
1345                            use azul_css::props::style::StyleBackgroundContent::*;
1346                            let css_image_id = match bg {
1347                                Image(i) => i,
1348                                _ => return None,
1349                            };
1350                            let image_ref = css_image_cache.get_css_image_id(css_image_id)?;
1351                            Some(image_ref.clone())
1352                        })
1353                        .collect();
1354                }
1355
1356                // If the node has a clip mask, it needs to be uploaded
1357                if let Some(clip_mask) = node_data.get_clip_mask() {
1358                    v.clip_mask = Some(clip_mask.image.clone());
1359                }
1360
1361                v
1362            })
1363            .collect::<Vec<_>>();
1364
1365        let mut set = FastBTreeSet::new();
1366
1367        for scan_image in images.into_iter() {
1368            if let Some(n) = scan_image.node_type_image {
1369                set.insert(n);
1370            }
1371            if let Some(n) = scan_image.clip_mask {
1372                set.insert(n);
1373            }
1374            for bg in scan_image.background_image {
1375                set.insert(bg);
1376            }
1377        }
1378
1379        set
1380    }
1381
1382    /// Updates hover state for nodes and returns changed CSS properties.
1383    #[must_use]
1384    pub fn restyle_nodes_hover(
1385        &mut self,
1386        nodes: &[NodeId],
1387        new_hover_state: bool,
1388    ) -> BTreeMap<NodeId, Vec<ChangedCssProperty>> {
1389        // save the old node state
1390        let old_node_states = nodes
1391            .iter()
1392            .map(|nid| {
1393                self.styled_nodes.as_container()[*nid]
1394                    .styled_node_state
1395                    .clone()
1396            })
1397            .collect::<Vec<_>>();
1398
1399        for nid in nodes.iter() {
1400            self.styled_nodes.as_container_mut()[*nid]
1401                .styled_node_state
1402                .hover = new_hover_state;
1403        }
1404
1405        let css_property_cache = self.get_css_property_cache();
1406        let styled_nodes = self.styled_nodes.as_container();
1407        let node_data = self.node_data.as_container();
1408
1409        let default_map = BTreeMap::default();
1410
1411        // scan all properties that could have changed because of addition / removal
1412        let v = nodes
1413            .iter()
1414            .zip(old_node_states.iter())
1415            .filter_map(|(node_id, old_node_state)| {
1416                let mut keys_normal: Vec<_> = css_property_cache
1417                    .css_hover_props
1418                    .get(node_id.index())
1419                    .unwrap_or(&default_map)
1420                    .keys()
1421                    .collect();
1422                let mut keys_inherited: Vec<_> = css_property_cache
1423                    .cascaded_hover_props
1424                    .get(node_id.index())
1425                    .unwrap_or(&default_map)
1426                    .keys()
1427                    .collect();
1428                let keys_inline: Vec<CssPropertyType> = {
1429                    use azul_css::dynamic_selector::{DynamicSelector, PseudoStateType};
1430                    node_data[*node_id]
1431                        .css_props
1432                        .iter()
1433                        .filter_map(|prop| {
1434                            let is_hover = prop.apply_if.as_slice().iter().any(|c| {
1435                                matches!(c, DynamicSelector::PseudoState(PseudoStateType::Hover))
1436                            });
1437                            if is_hover {
1438                                Some(prop.property.get_type())
1439                            } else {
1440                                None
1441                            }
1442                        })
1443                        .collect()
1444                };
1445                let mut keys_inline_ref = keys_inline.iter().map(|r| r).collect();
1446
1447                keys_normal.append(&mut keys_inherited);
1448                keys_normal.append(&mut keys_inline_ref);
1449
1450                let node_properties_that_could_have_changed = keys_normal;
1451
1452                if node_properties_that_could_have_changed.is_empty() {
1453                    return None;
1454                }
1455
1456                let new_node_state = &styled_nodes[*node_id].styled_node_state;
1457                let node_data = &node_data[*node_id];
1458
1459                let changes = node_properties_that_could_have_changed
1460                    .into_iter()
1461                    .filter_map(|prop| {
1462                        // calculate both the old and the new state
1463                        let old = css_property_cache.get_property(
1464                            node_data,
1465                            node_id,
1466                            old_node_state,
1467                            prop,
1468                        );
1469                        let new = css_property_cache.get_property(
1470                            node_data,
1471                            node_id,
1472                            new_node_state,
1473                            prop,
1474                        );
1475                        if old == new {
1476                            None
1477                        } else {
1478                            Some(ChangedCssProperty {
1479                                previous_state: old_node_state.clone(),
1480                                previous_prop: match old {
1481                                    None => CssProperty::auto(*prop),
1482                                    Some(s) => s.clone(),
1483                                },
1484                                current_state: new_node_state.clone(),
1485                                current_prop: match new {
1486                                    None => CssProperty::auto(*prop),
1487                                    Some(s) => s.clone(),
1488                                },
1489                            })
1490                        }
1491                    })
1492                    .collect::<Vec<_>>();
1493
1494                if changes.is_empty() {
1495                    None
1496                } else {
1497                    Some((*node_id, changes))
1498                }
1499            })
1500            .collect::<Vec<_>>();
1501
1502        v.into_iter().collect()
1503    }
1504
1505    /// Updates active state for nodes and returns changed CSS properties.
1506    #[must_use]
1507    pub fn restyle_nodes_active(
1508        &mut self,
1509        nodes: &[NodeId],
1510        new_active_state: bool,
1511    ) -> BTreeMap<NodeId, Vec<ChangedCssProperty>> {
1512        // save the old node state
1513        let old_node_states = nodes
1514            .iter()
1515            .map(|nid| {
1516                self.styled_nodes.as_container()[*nid]
1517                    .styled_node_state
1518                    .clone()
1519            })
1520            .collect::<Vec<_>>();
1521
1522        for nid in nodes.iter() {
1523            self.styled_nodes.as_container_mut()[*nid]
1524                .styled_node_state
1525                .active = new_active_state;
1526        }
1527
1528        let css_property_cache = self.get_css_property_cache();
1529        let styled_nodes = self.styled_nodes.as_container();
1530        let node_data = self.node_data.as_container();
1531
1532        let default_map = BTreeMap::default();
1533
1534        // scan all properties that could have changed because of addition / removal
1535        let v = nodes
1536            .iter()
1537            .zip(old_node_states.iter())
1538            .filter_map(|(node_id, old_node_state)| {
1539                let mut keys_normal: Vec<_> = css_property_cache
1540                    .css_active_props
1541                    .get(node_id.index())
1542                    .unwrap_or(&default_map)
1543                    .keys()
1544                    .collect();
1545
1546                let mut keys_inherited: Vec<_> = css_property_cache
1547                    .cascaded_active_props
1548                    .get(node_id.index())
1549                    .unwrap_or(&default_map)
1550                    .keys()
1551                    .collect();
1552
1553                let keys_inline: Vec<CssPropertyType> = {
1554                    use azul_css::dynamic_selector::{DynamicSelector, PseudoStateType};
1555                    node_data[*node_id]
1556                        .css_props
1557                        .iter()
1558                        .filter_map(|prop| {
1559                            let is_active = prop.apply_if.as_slice().iter().any(|c| {
1560                                matches!(c, DynamicSelector::PseudoState(PseudoStateType::Active))
1561                            });
1562                            if is_active {
1563                                Some(prop.property.get_type())
1564                            } else {
1565                                None
1566                            }
1567                        })
1568                        .collect()
1569                };
1570                let mut keys_inline_ref = keys_inline.iter().map(|r| r).collect();
1571
1572                keys_normal.append(&mut keys_inherited);
1573                keys_normal.append(&mut keys_inline_ref);
1574
1575                let node_properties_that_could_have_changed = keys_normal;
1576
1577                if node_properties_that_could_have_changed.is_empty() {
1578                    return None;
1579                }
1580
1581                let new_node_state = &styled_nodes[*node_id].styled_node_state;
1582                let node_data = &node_data[*node_id];
1583
1584                let changes = node_properties_that_could_have_changed
1585                    .into_iter()
1586                    .filter_map(|prop| {
1587                        // calculate both the old and the new state
1588                        let old = css_property_cache.get_property(
1589                            node_data,
1590                            node_id,
1591                            old_node_state,
1592                            prop,
1593                        );
1594                        let new = css_property_cache.get_property(
1595                            node_data,
1596                            node_id,
1597                            new_node_state,
1598                            prop,
1599                        );
1600                        if old == new {
1601                            None
1602                        } else {
1603                            Some(ChangedCssProperty {
1604                                previous_state: old_node_state.clone(),
1605                                previous_prop: match old {
1606                                    None => CssProperty::auto(*prop),
1607                                    Some(s) => s.clone(),
1608                                },
1609                                current_state: new_node_state.clone(),
1610                                current_prop: match new {
1611                                    None => CssProperty::auto(*prop),
1612                                    Some(s) => s.clone(),
1613                                },
1614                            })
1615                        }
1616                    })
1617                    .collect::<Vec<_>>();
1618
1619                if changes.is_empty() {
1620                    None
1621                } else {
1622                    Some((*node_id, changes))
1623                }
1624            })
1625            .collect::<Vec<_>>();
1626
1627        v.into_iter().collect()
1628    }
1629
1630    /// Updates focus state for nodes and returns changed CSS properties.
1631    #[must_use]
1632    pub fn restyle_nodes_focus(
1633        &mut self,
1634        nodes: &[NodeId],
1635        new_focus_state: bool,
1636    ) -> BTreeMap<NodeId, Vec<ChangedCssProperty>> {
1637        
1638        // save the old node state
1639        let old_node_states = nodes
1640            .iter()
1641            .map(|nid| {
1642                let state = self.styled_nodes.as_container()[*nid]
1643                    .styled_node_state
1644                    .clone();
1645                state
1646            })
1647            .collect::<Vec<_>>();
1648
1649        for nid in nodes.iter() {
1650            self.styled_nodes.as_container_mut()[*nid]
1651                .styled_node_state
1652                .focused = new_focus_state;
1653        }
1654
1655        let css_property_cache = self.get_css_property_cache();
1656        let styled_nodes = self.styled_nodes.as_container();
1657        let node_data = self.node_data.as_container();
1658
1659        let default_map = BTreeMap::default();
1660
1661        // scan all properties that could have changed because of addition / removal
1662        let v = nodes
1663            .iter()
1664            .zip(old_node_states.iter())
1665            .filter_map(|(node_id, old_node_state)| {
1666                let mut keys_normal: Vec<_> = css_property_cache
1667                    .css_focus_props
1668                    .get(node_id.index())
1669                    .unwrap_or(&default_map)
1670                    .keys()
1671                    .collect();
1672                
1673
1674                let mut keys_inherited: Vec<_> = css_property_cache
1675                    .cascaded_focus_props
1676                    .get(node_id.index())
1677                    .unwrap_or(&default_map)
1678                    .keys()
1679                    .collect();
1680                
1681
1682                let keys_inline: Vec<CssPropertyType> = {
1683                    use azul_css::dynamic_selector::{DynamicSelector, PseudoStateType};
1684                    node_data[*node_id]
1685                        .css_props
1686                        .iter()
1687                        .filter_map(|prop| {
1688                            let is_focus = prop.apply_if.as_slice().iter().any(|c| {
1689                                matches!(c, DynamicSelector::PseudoState(PseudoStateType::Focus))
1690                            });
1691                            if is_focus {
1692                                Some(prop.property.get_type())
1693                            } else {
1694                                None
1695                            }
1696                        })
1697                        .collect()
1698                };
1699                let mut keys_inline_ref = keys_inline.iter().map(|r| r).collect();
1700
1701                keys_normal.append(&mut keys_inherited);
1702                keys_normal.append(&mut keys_inline_ref);
1703
1704                let node_properties_that_could_have_changed = keys_normal;
1705                
1706
1707                if node_properties_that_could_have_changed.is_empty() {
1708                    return None;
1709                }
1710
1711                let new_node_state = &styled_nodes[*node_id].styled_node_state;
1712                let node_data = &node_data[*node_id];
1713
1714                let changes = node_properties_that_could_have_changed
1715                    .into_iter()
1716                    .filter_map(|prop| {
1717                        // calculate both the old and the new state
1718                        let old = css_property_cache.get_property(
1719                            node_data,
1720                            node_id,
1721                            old_node_state,
1722                            prop,
1723                        );
1724                        let new = css_property_cache.get_property(
1725                            node_data,
1726                            node_id,
1727                            new_node_state,
1728                            prop,
1729                        );
1730                        if old == new {
1731                            None
1732                        } else {
1733                            Some(ChangedCssProperty {
1734                                previous_state: old_node_state.clone(),
1735                                previous_prop: match old {
1736                                    None => CssProperty::auto(*prop),
1737                                    Some(s) => s.clone(),
1738                                },
1739                                current_state: new_node_state.clone(),
1740                                current_prop: match new {
1741                                    None => CssProperty::auto(*prop),
1742                                    Some(s) => s.clone(),
1743                                },
1744                            })
1745                        }
1746                    })
1747                    .collect::<Vec<_>>();
1748
1749                if changes.is_empty() {
1750                    None
1751                } else {
1752                    Some((*node_id, changes))
1753                }
1754            })
1755            .collect::<Vec<_>>();
1756
1757        v.into_iter().collect()
1758    }
1759
1760    /// Unified entry point for all CSS restyle operations.
1761    ///
1762    /// This function synchronizes the StyledNodeState with runtime state
1763    /// and computes which CSS properties have changed. It determines whether
1764    /// layout, display list, or GPU-only updates are needed.
1765    ///
1766    /// # Arguments
1767    /// * `focus_changes` - Nodes gaining/losing focus
1768    /// * `hover_changes` - Nodes gaining/losing hover
1769    /// * `active_changes` - Nodes gaining/losing active (mouse down)
1770    ///
1771    /// # Returns
1772    /// * `RestyleResult` containing changed nodes and what needs updating
1773    #[must_use]
1774    pub fn restyle_on_state_change(
1775        &mut self,
1776        focus_changes: Option<FocusChange>,
1777        hover_changes: Option<HoverChange>,
1778        active_changes: Option<ActiveChange>,
1779    ) -> RestyleResult {
1780        
1781        let mut result = RestyleResult::default();
1782        result.gpu_only_changes = true; // Start with GPU-only assumption
1783
1784        // Helper closure to merge changes and analyze property categories
1785        let mut process_changes = |changes: BTreeMap<NodeId, Vec<ChangedCssProperty>>| {
1786            for (node_id, props) in changes {
1787                for change in &props {
1788                    let prop_type = change.current_prop.get_type();
1789
1790                    // Use the granular RelayoutScope instead of the binary
1791                    // can_trigger_relayout(). We pass node_is_ifc_member = true
1792                    // conservatively: this means font/text property changes will
1793                    // produce IfcOnly (rather than None). Phase 2c can refine
1794                    // this by checking whether the node actually participates
1795                    // in an IFC.
1796                    let scope = prop_type.relayout_scope(/* node_is_ifc_member */ true);
1797
1798                    // Track the highest scope seen
1799                    if scope > result.max_relayout_scope {
1800                        result.max_relayout_scope = scope;
1801                    }
1802
1803                    // Any scope above None triggers layout
1804                    if scope != RelayoutScope::None {
1805                        result.needs_layout = true;
1806                        result.gpu_only_changes = false;
1807                    }
1808                    
1809                    // Check if this is a GPU-only property
1810                    if !prop_type.is_gpu_only_property() {
1811                        result.gpu_only_changes = false;
1812                    }
1813                    
1814                    // Any visual change needs display list update (unless GPU-only)
1815                    result.needs_display_list = true;
1816                }
1817                
1818                result.changed_nodes.entry(node_id).or_default().extend(props);
1819            }
1820        };
1821
1822        // 1. Process focus changes
1823        if let Some(focus) = focus_changes {
1824            if let Some(old) = focus.lost_focus {
1825                let changes = self.restyle_nodes_focus(&[old], false);
1826                process_changes(changes);
1827            }
1828            if let Some(new) = focus.gained_focus {
1829                let changes = self.restyle_nodes_focus(&[new], true);
1830                process_changes(changes);
1831            }
1832        }
1833
1834        // 2. Process hover changes
1835        if let Some(hover) = hover_changes {
1836            if !hover.left_nodes.is_empty() {
1837                let changes = self.restyle_nodes_hover(&hover.left_nodes, false);
1838                process_changes(changes);
1839            }
1840            if !hover.entered_nodes.is_empty() {
1841                let changes = self.restyle_nodes_hover(&hover.entered_nodes, true);
1842                process_changes(changes);
1843            }
1844        }
1845
1846        // 3. Process active changes
1847        if let Some(active) = active_changes {
1848            if !active.deactivated.is_empty() {
1849                let changes = self.restyle_nodes_active(&active.deactivated, false);
1850                process_changes(changes);
1851            }
1852            if !active.activated.is_empty() {
1853                let changes = self.restyle_nodes_active(&active.activated, true);
1854                process_changes(changes);
1855            }
1856        }
1857
1858        // If no changes, reset display_list flag
1859        if result.changed_nodes.is_empty() {
1860            result.needs_display_list = false;
1861            result.gpu_only_changes = false;
1862        }
1863        
1864        // If layout is needed, display list is also needed
1865        if result.needs_layout {
1866            result.needs_display_list = true;
1867            result.gpu_only_changes = false;
1868        }
1869
1870        result
1871    }
1872
1873    /// Overrides CSS properties for a node and returns changed properties.
1874    // Inserts a property into the self.user_overridden_properties
1875    #[must_use]
1876    pub fn restyle_user_property(
1877        &mut self,
1878        node_id: &NodeId,
1879        new_properties: &[CssProperty],
1880    ) -> BTreeMap<NodeId, Vec<ChangedCssProperty>> {
1881        let mut map = BTreeMap::default();
1882
1883        if new_properties.is_empty() {
1884            return map;
1885        }
1886
1887        let node_data = self.node_data.as_container();
1888        let node_data = &node_data[*node_id];
1889
1890        let node_states = &self.styled_nodes.as_container();
1891        let old_node_state = &node_states[*node_id].styled_node_state;
1892
1893        let changes: Vec<ChangedCssProperty> = {
1894            let css_property_cache = self.get_css_property_cache();
1895
1896            new_properties
1897                .iter()
1898                .filter_map(|new_prop| {
1899                    let old_prop = css_property_cache.get_property(
1900                        node_data,
1901                        node_id,
1902                        old_node_state,
1903                        &new_prop.get_type(),
1904                    );
1905
1906                    let old_prop = match old_prop {
1907                        None => CssProperty::auto(new_prop.get_type()),
1908                        Some(s) => s.clone(),
1909                    };
1910
1911                    if old_prop == *new_prop {
1912                        None
1913                    } else {
1914                        Some(ChangedCssProperty {
1915                            previous_state: old_node_state.clone(),
1916                            previous_prop: old_prop,
1917                            // overriding a user property does not change the state
1918                            current_state: old_node_state.clone(),
1919                            current_prop: new_prop.clone(),
1920                        })
1921                    }
1922                })
1923                .collect()
1924        };
1925
1926        let css_property_cache_mut = self.get_css_property_cache_mut();
1927
1928        for new_prop in new_properties.iter() {
1929            if new_prop.is_initial() {
1930                let map = &mut css_property_cache_mut
1931                    .user_overridden_properties[node_id.index()];
1932                // CssProperty::Initial = remove overridden property
1933                map.remove(&new_prop.get_type());
1934            } else {
1935                css_property_cache_mut
1936                    .user_overridden_properties[node_id.index()]
1937                    .insert(new_prop.get_type(), new_prop.clone());
1938            }
1939        }
1940
1941        if !changes.is_empty() {
1942            map.insert(*node_id, changes);
1943        }
1944
1945        map
1946    }
1947
1948    /// Scans the `StyledDom` for iframe callbacks
1949    pub fn scan_for_iframe_callbacks(&self) -> Vec<NodeId> {
1950        use crate::dom::NodeType;
1951        self.node_data
1952            .as_ref()
1953            .iter()
1954            .enumerate()
1955            .filter_map(|(node_id, node_data)| match node_data.get_node_type() {
1956                NodeType::IFrame(_) => Some(NodeId::new(node_id)),
1957                _ => None,
1958            })
1959            .collect()
1960    }
1961
1962    /// Scans the `StyledDom` for OpenGL callbacks
1963    pub fn scan_for_gltexture_callbacks(&self) -> Vec<NodeId> {
1964        use crate::dom::NodeType;
1965        self.node_data
1966            .as_ref()
1967            .iter()
1968            .enumerate()
1969            .filter_map(|(node_id, node_data)| {
1970                use crate::resources::DecodedImage;
1971                match node_data.get_node_type() {
1972                    NodeType::Image(image_ref) => {
1973                        if let DecodedImage::Callback(_) = image_ref.get_data() {
1974                            Some(NodeId::new(node_id))
1975                        } else {
1976                            None
1977                        }
1978                    }
1979                    _ => None,
1980                }
1981            })
1982            .collect()
1983    }
1984
1985    /// Returns a HTML-formatted version of the DOM for easier debugging.
1986    ///
1987    /// For example, a DOM with a parent div containing a child div would return:
1988    ///
1989    /// ```xml,no_run,ignore
1990    /// <div id="hello">
1991    ///      <div id="test" />
1992    /// </div>
1993    /// ```
1994    pub fn get_html_string(&self, custom_head: &str, custom_body: &str, test_mode: bool) -> String {
1995        let css_property_cache = self.get_css_property_cache();
1996
1997        let mut output = String::new();
1998
1999        // After which nodes should a close tag be printed?
2000        let mut should_print_close_tag_after_node = BTreeMap::new();
2001
2002        let should_print_close_tag_debug = self
2003            .non_leaf_nodes
2004            .iter()
2005            .filter_map(|p| {
2006                let parent_node_id = p.node_id.into_crate_internal()?;
2007                let mut total_last_child = None;
2008                recursive_get_last_child(
2009                    parent_node_id,
2010                    &self.node_hierarchy.as_ref(),
2011                    &mut total_last_child,
2012                );
2013                let total_last_child = total_last_child?;
2014                Some((parent_node_id, (total_last_child, p.depth)))
2015            })
2016            .collect::<BTreeMap<_, _>>();
2017
2018        for (parent_id, (last_child, parent_depth)) in should_print_close_tag_debug {
2019            should_print_close_tag_after_node
2020                .entry(last_child)
2021                .or_insert_with(|| Vec::new())
2022                .push((parent_id, parent_depth));
2023        }
2024
2025        let mut all_node_depths = self
2026            .non_leaf_nodes
2027            .iter()
2028            .filter_map(|p| {
2029                let parent_node_id = p.node_id.into_crate_internal()?;
2030                Some((parent_node_id, p.depth))
2031            })
2032            .collect::<BTreeMap<_, _>>();
2033
2034        for (parent_node_id, parent_depth) in self
2035            .non_leaf_nodes
2036            .iter()
2037            .filter_map(|p| Some((p.node_id.into_crate_internal()?, p.depth)))
2038        {
2039            for child_id in parent_node_id.az_children(&self.node_hierarchy.as_container()) {
2040                all_node_depths.insert(child_id, parent_depth + 1);
2041            }
2042        }
2043
2044        for node_id in self.node_hierarchy.as_container().linear_iter() {
2045            let depth = all_node_depths[&node_id];
2046
2047            let node_data = &self.node_data.as_container()[node_id];
2048            let node_state = &self.styled_nodes.as_container()[node_id].styled_node_state;
2049            let tabs = String::from("    ").repeat(depth);
2050
2051            output.push_str("\r\n");
2052            output.push_str(&tabs);
2053            output.push_str(&node_data.debug_print_start(css_property_cache, &node_id, node_state));
2054
2055            if let Some(content) = node_data.get_node_type().format().as_ref() {
2056                output.push_str(content);
2057            }
2058
2059            let node_has_children = self.node_hierarchy.as_container()[node_id]
2060                .first_child_id(node_id)
2061                .is_some();
2062            if !node_has_children {
2063                let node_data = &self.node_data.as_container()[node_id];
2064                output.push_str(&node_data.debug_print_end());
2065            }
2066
2067            if let Some(close_tag_vec) = should_print_close_tag_after_node.get(&node_id) {
2068                let mut close_tag_vec = close_tag_vec.clone();
2069                close_tag_vec.sort_by(|a, b| b.1.cmp(&a.1)); // sort by depth descending
2070                for (close_tag_parent_id, close_tag_depth) in close_tag_vec {
2071                    let node_data = &self.node_data.as_container()[close_tag_parent_id];
2072                    let tabs = String::from("    ").repeat(close_tag_depth);
2073                    output.push_str("\r\n");
2074                    output.push_str(&tabs);
2075                    output.push_str(&node_data.debug_print_end());
2076                }
2077            }
2078        }
2079
2080        if !test_mode {
2081            format!(
2082                "
2083                <html>
2084                    <head>
2085                    <style>* {{ margin:0px; padding:0px; }}</style>
2086                    {custom_head}
2087                    </head>
2088                {output}
2089                {custom_body}
2090                </html>
2091            "
2092            )
2093        } else {
2094            output
2095        }
2096    }
2097
2098    /// Returns the node ID of all sub-children of a node
2099    pub fn get_subtree(&self, parent: NodeId) -> Vec<NodeId> {
2100        let mut total_last_child = None;
2101        recursive_get_last_child(parent, &self.node_hierarchy.as_ref(), &mut total_last_child);
2102        if let Some(last) = total_last_child {
2103            (parent.index()..=last.index())
2104                .map(|id| NodeId::new(id))
2105                .collect()
2106        } else {
2107            Vec::new()
2108        }
2109    }
2110
2111    /// Returns node IDs of all parent nodes in the subtree (nodes with children).
2112    // Same as get_subtree, but only returns parents
2113    pub fn get_subtree_parents(&self, parent: NodeId) -> Vec<NodeId> {
2114        let mut total_last_child = None;
2115        recursive_get_last_child(parent, &self.node_hierarchy.as_ref(), &mut total_last_child);
2116        if let Some(last) = total_last_child {
2117            (parent.index()..=last.index())
2118                .filter_map(|id| {
2119                    if self.node_hierarchy.as_ref()[id].last_child_id().is_some() {
2120                        Some(NodeId::new(id))
2121                    } else {
2122                        None
2123                    }
2124                })
2125                .collect()
2126        } else {
2127            Vec::new()
2128        }
2129    }
2130
2131    /// Returns nodes grouped by their rendering order (respects z-index and position).
2132    pub fn get_rects_in_rendering_order(&self) -> ContentGroup {
2133        Self::determine_rendering_order(
2134            &self.non_leaf_nodes.as_ref(),
2135            &self.node_hierarchy.as_container(),
2136            &self.styled_nodes.as_container(),
2137            &self.node_data.as_container(),
2138            &self.get_css_property_cache(),
2139        )
2140    }
2141
2142    /// Returns the rendering order of the items (the rendering
2143    /// order doesn't have to be the original order)
2144    fn determine_rendering_order<'a>(
2145        non_leaf_nodes: &[ParentWithNodeDepth],
2146        node_hierarchy: &NodeDataContainerRef<'a, NodeHierarchyItem>,
2147        styled_nodes: &NodeDataContainerRef<StyledNode>,
2148        node_data_container: &NodeDataContainerRef<NodeData>,
2149        css_property_cache: &CssPropertyCache,
2150    ) -> ContentGroup {
2151        let children_sorted = non_leaf_nodes
2152            .iter()
2153            .filter_map(|parent| {
2154                Some((
2155                    parent.node_id,
2156                    sort_children_by_position(
2157                        parent.node_id.into_crate_internal()?,
2158                        node_hierarchy,
2159                        styled_nodes,
2160                        node_data_container,
2161                        css_property_cache,
2162                    ),
2163                ))
2164            })
2165            .collect::<Vec<_>>();
2166
2167        let children_sorted: BTreeMap<NodeHierarchyItemId, Vec<NodeHierarchyItemId>> =
2168            children_sorted.into_iter().collect();
2169
2170        let mut root_content_group = ContentGroup {
2171            root: NodeHierarchyItemId::from_crate_internal(Some(NodeId::ZERO)),
2172            children: Vec::new().into(),
2173        };
2174
2175        fill_content_group_children(&mut root_content_group, &children_sorted);
2176
2177        root_content_group
2178    }
2179
2180    /// Replaces this StyledDom with default and returns the old value.
2181    pub fn swap_with_default(&mut self) -> Self {
2182        let mut new = Self::default();
2183        core::mem::swap(self, &mut new);
2184        new
2185    }
2186
2187    // Computes the diff between the two DOMs
2188    // pub fn diff(&self, other: &Self) -> StyledDomDiff { /**/ }
2189}
2190
2191/// Same as `Dom`, but arena-based for more efficient memory layout and faster traversal.
2192#[derive(Debug, PartialEq, PartialOrd, Eq)]
2193pub struct CompactDom {
2194    /// The arena containing the hierarchical relationships (parent, child, sibling) of all nodes.
2195    pub node_hierarchy: NodeHierarchy,
2196    /// The arena containing the actual data (`NodeData`) for each node.
2197    pub node_data: NodeDataContainer<NodeData>,
2198    /// The ID of the root node of the DOM tree.
2199    pub root: NodeId,
2200}
2201
2202impl CompactDom {
2203    /// Returns the number of nodes in this DOM.
2204    #[inline(always)]
2205    pub fn len(&self) -> usize {
2206        self.node_hierarchy.as_ref().len()
2207    }
2208}
2209
2210impl From<Dom> for CompactDom {
2211    fn from(dom: Dom) -> Self {
2212        convert_dom_into_compact_dom(dom)
2213    }
2214}
2215
2216/// Converts a tree-based Dom into an arena-based CompactDom for efficient traversal.
2217pub fn convert_dom_into_compact_dom(mut dom: Dom) -> CompactDom {
2218    // note: somehow convert this into a non-recursive form later on!
2219    fn convert_dom_into_compact_dom_internal(
2220        dom: &mut Dom,
2221        node_hierarchy: &mut [Node],
2222        node_data: &mut Vec<NodeData>,
2223        parent_node_id: NodeId,
2224        node: Node,
2225        cur_node_id: &mut usize,
2226    ) {
2227        // - parent [0]
2228        //    - child [1]
2229        //    - child [2]
2230        //        - child of child 2 [2]
2231        //        - child of child 2 [4]
2232        //    - child [5]
2233        //    - child [6]
2234        //        - child of child 4 [7]
2235
2236        // Write node into the arena here!
2237        node_hierarchy[parent_node_id.index()] = node.clone();
2238
2239        let copy = dom.root.copy_special();
2240
2241        node_data[parent_node_id.index()] = copy;
2242
2243        *cur_node_id += 1;
2244
2245        let mut previous_sibling_id = None;
2246        let children_len = dom.children.len();
2247        for (child_index, child_dom) in dom.children.as_mut().iter_mut().enumerate() {
2248            let child_node_id = NodeId::new(*cur_node_id);
2249            let is_last_child = (child_index + 1) == children_len;
2250            let child_dom_is_empty = child_dom.children.is_empty();
2251            let child_node = Node {
2252                parent: Some(parent_node_id),
2253                previous_sibling: previous_sibling_id,
2254                next_sibling: if is_last_child {
2255                    None
2256                } else {
2257                    Some(child_node_id + child_dom.estimated_total_children + 1)
2258                },
2259                last_child: if child_dom_is_empty {
2260                    None
2261                } else {
2262                    Some(child_node_id + child_dom.estimated_total_children)
2263                },
2264            };
2265            previous_sibling_id = Some(child_node_id);
2266            // recurse BEFORE adding the next child
2267            convert_dom_into_compact_dom_internal(
2268                child_dom,
2269                node_hierarchy,
2270                node_data,
2271                child_node_id,
2272                child_node,
2273                cur_node_id,
2274            );
2275        }
2276    }
2277
2278    // Pre-allocate all nodes (+ 1 root node)
2279    const DEFAULT_NODE_DATA: NodeData = NodeData::create_div();
2280
2281    let sum_nodes = dom.fixup_children_estimated();
2282
2283    let mut node_hierarchy = vec![Node::ROOT; sum_nodes + 1];
2284    let mut node_data = vec![NodeData::create_div(); sum_nodes + 1];
2285    let mut cur_node_id = 0;
2286
2287    let root_node_id = NodeId::ZERO;
2288    let root_node = Node {
2289        parent: None,
2290        previous_sibling: None,
2291        next_sibling: None,
2292        last_child: if dom.children.is_empty() {
2293            None
2294        } else {
2295            Some(root_node_id + dom.estimated_total_children)
2296        },
2297    };
2298
2299    convert_dom_into_compact_dom_internal(
2300        &mut dom,
2301        &mut node_hierarchy,
2302        &mut node_data,
2303        root_node_id,
2304        root_node,
2305        &mut cur_node_id,
2306    );
2307
2308    CompactDom {
2309        node_hierarchy: NodeHierarchy {
2310            internal: node_hierarchy,
2311        },
2312        node_data: NodeDataContainer {
2313            internal: node_data,
2314        },
2315        root: root_node_id,
2316    }
2317}
2318
2319fn fill_content_group_children(
2320    group: &mut ContentGroup,
2321    children_sorted: &BTreeMap<NodeHierarchyItemId, Vec<NodeHierarchyItemId>>,
2322) {
2323    if let Some(c) = children_sorted.get(&group.root) {
2324        // returns None for leaf nodes
2325        group.children = c
2326            .iter()
2327            .map(|child| ContentGroup {
2328                root: *child,
2329                children: Vec::new().into(),
2330            })
2331            .collect::<Vec<ContentGroup>>()
2332            .into();
2333
2334        for c in group.children.as_mut() {
2335            fill_content_group_children(c, children_sorted);
2336        }
2337    }
2338}
2339
2340fn sort_children_by_position<'a>(
2341    parent: NodeId,
2342    node_hierarchy: &NodeDataContainerRef<'a, NodeHierarchyItem>,
2343    rectangles: &NodeDataContainerRef<StyledNode>,
2344    node_data_container: &NodeDataContainerRef<NodeData>,
2345    css_property_cache: &CssPropertyCache,
2346) -> Vec<NodeHierarchyItemId> {
2347    use azul_css::props::layout::LayoutPosition::*;
2348
2349    let children_positions = parent
2350        .az_children(node_hierarchy)
2351        .map(|nid| {
2352            let position = css_property_cache
2353                .get_position(
2354                    &node_data_container[nid],
2355                    &nid,
2356                    &rectangles[nid].styled_node_state,
2357                )
2358                .and_then(|p| p.clone().get_property_or_default())
2359                .unwrap_or_default();
2360            let id = NodeHierarchyItemId::from_crate_internal(Some(nid));
2361            (id, position)
2362        })
2363        .collect::<Vec<_>>();
2364
2365    let mut not_absolute_children = children_positions
2366        .iter()
2367        .filter_map(|(node_id, position)| {
2368            if *position != Absolute {
2369                Some(*node_id)
2370            } else {
2371                None
2372            }
2373        })
2374        .collect::<Vec<_>>();
2375
2376    let mut absolute_children = children_positions
2377        .iter()
2378        .filter_map(|(node_id, position)| {
2379            if *position == Absolute {
2380                Some(*node_id)
2381            } else {
2382                None
2383            }
2384        })
2385        .collect::<Vec<_>>();
2386
2387    // Append the position:absolute children after the regular children
2388    not_absolute_children.append(&mut absolute_children);
2389    not_absolute_children
2390}
2391
2392// calls get_last_child() recursively until the last child of the last child of the ... has been
2393// found
2394fn recursive_get_last_child(
2395    node_id: NodeId,
2396    node_hierarchy: &[NodeHierarchyItem],
2397    target: &mut Option<NodeId>,
2398) {
2399    match node_hierarchy[node_id.index()].last_child_id() {
2400        None => return,
2401        Some(s) => {
2402            *target = Some(s);
2403            recursive_get_last_child(s, node_hierarchy, target);
2404        }
2405    }
2406}
2407
2408// ============================================================================
2409// DOM TRAVERSAL FOR MULTI-NODE SELECTION
2410// ============================================================================
2411
2412/// Determine if node_a comes before node_b in document order.
2413///
2414/// Document order is defined as pre-order depth-first traversal order.
2415/// This is equivalent to the order nodes appear in HTML source.
2416///
2417/// ## Algorithm
2418/// 1. Find the path from root to each node
2419/// 2. Find the Lowest Common Ancestor (LCA)
2420/// 3. At the divergence point, the child that appears first in sibling order comes first
2421pub fn is_before_in_document_order(
2422    hierarchy: &NodeHierarchyItemVec,
2423    node_a: NodeId,
2424    node_b: NodeId,
2425) -> bool {
2426    if node_a == node_b {
2427        return false;
2428    }
2429    
2430    let hierarchy = hierarchy.as_container();
2431    
2432    // Get paths from root to each node (stored as root-first order)
2433    let path_a = get_path_to_root(&hierarchy, node_a);
2434    let path_b = get_path_to_root(&hierarchy, node_b);
2435    
2436    // Find divergence point (last common ancestor)
2437    let min_len = path_a.len().min(path_b.len());
2438    
2439    for i in 0..min_len {
2440        if path_a[i] != path_b[i] {
2441            // Found divergence - check which sibling comes first
2442            let child_towards_a = path_a[i];
2443            let child_towards_b = path_b[i];
2444            
2445            // A smaller NodeId index means it was created earlier in DOM construction,
2446            // which means it comes first in document order for siblings
2447            return child_towards_a.index() < child_towards_b.index();
2448        }
2449    }
2450    
2451    // One path is a prefix of the other - the shorter path (ancestor) comes first
2452    path_a.len() < path_b.len()
2453}
2454
2455/// Get the path from root to a node, returned in root-first order.
2456fn get_path_to_root(
2457    hierarchy: &NodeDataContainerRef<'_, NodeHierarchyItem>,
2458    node: NodeId,
2459) -> Vec<NodeId> {
2460    let mut path = Vec::new();
2461    let mut current = Some(node);
2462    
2463    while let Some(node_id) = current {
2464        path.push(node_id);
2465        current = hierarchy.get(node_id).and_then(|h| h.parent_id());
2466    }
2467    
2468    // Reverse to get root-first order
2469    path.reverse();
2470    path
2471}
2472
2473/// Collect all nodes between start and end (inclusive) in document order.
2474///
2475/// This performs a pre-order depth-first traversal starting from the root,
2476/// collecting nodes once we've seen `start` and stopping at `end`.
2477///
2478/// ## Parameters
2479/// * `hierarchy` - The node hierarchy
2480/// * `start_node` - First node in document order
2481/// * `end_node` - Last node in document order
2482///
2483/// ## Returns
2484/// Vector of NodeIds in document order, from start to end (inclusive)
2485pub fn collect_nodes_in_document_order(
2486    hierarchy: &NodeHierarchyItemVec,
2487    start_node: NodeId,
2488    end_node: NodeId,
2489) -> Vec<NodeId> {
2490    if start_node == end_node {
2491        return vec![start_node];
2492    }
2493    
2494    let hierarchy_container = hierarchy.as_container();
2495    let hierarchy_slice = hierarchy.as_ref();
2496    
2497    let mut result = Vec::new();
2498    let mut in_range = false;
2499    
2500    // Pre-order DFS using a stack
2501    // We need to traverse in document order, which is pre-order DFS
2502    let mut stack: Vec<NodeId> = vec![NodeId::ZERO]; // Start from root
2503    
2504    while let Some(current) = stack.pop() {
2505        // Check if we've entered the range
2506        if current == start_node {
2507            in_range = true;
2508        }
2509        
2510        // Collect if in range
2511        if in_range {
2512            result.push(current);
2513        }
2514        
2515        // Check if we've exited the range
2516        if current == end_node {
2517            break;
2518        }
2519        
2520        // Push children in reverse order so they pop in correct order
2521        // (first child should be processed first)
2522        if let Some(item) = hierarchy_container.get(current) {
2523            // Get first child
2524            if let Some(first_child) = item.first_child_id(current) {
2525                // Collect all children by following next_sibling
2526                let mut children = Vec::new();
2527                let mut child = Some(first_child);
2528                while let Some(child_id) = child {
2529                    children.push(child_id);
2530                    child = hierarchy_container.get(child_id).and_then(|h| h.next_sibling_id());
2531                }
2532                // Push in reverse order for correct DFS order
2533                for child_id in children.into_iter().rev() {
2534                    stack.push(child_id);
2535                }
2536            }
2537        }
2538    }
2539    
2540    result
2541}