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