Skip to main content

azul_core/
compact_cache_builder.rs

1//! Builder function to convert CssPropertyCache → CompactLayoutCache.
2//!
3//! Called once after restyle + apply_ua_css + compute_inherited_values.
4//! Uses typed getters on CssPropertyCache (which cascade through all sources)
5//! to resolve each property for the "normal" state (all pseudo-states = false).
6
7use crate::dom::{NodeData, NodeId};
8use crate::prop_cache::CssPropertyCache;
9use crate::styled_dom::StyledNodeState;
10use azul_css::compact_cache::*;
11use azul_css::css::CssPropertyValue;
12use azul_css::props::basic::length::SizeMetric;
13use azul_css::props::layout::dimensions::{LayoutHeight, LayoutWidth};
14use azul_css::props::layout::flex::LayoutFlexBasis;
15use azul_css::props::layout::position::LayoutZIndex;
16use core::hash::{Hash, Hasher};
17use std::collections::hash_map::DefaultHasher;
18
19impl CssPropertyCache {
20    /// Build a CompactLayoutCache from the current property cache state.
21    ///
22    /// Must be called after `restyle()`, `apply_ua_css()`, and `compute_inherited_values()`.
23    /// Resolves all layout-relevant properties for every node in the "normal" state
24    /// (no hover/active/focus) and encodes them into compact arrays.
25    ///
26    /// For the initial layout pass (where all nodes are in normal state), this provides
27    /// O(1) array-indexed access to all properties instead of BTreeMap lookups.
28    pub fn build_compact_cache(&self, node_data: &[NodeData]) -> CompactLayoutCache {
29        let node_count = self.node_count;
30        let default_state = StyledNodeState::default();
31        let mut result = CompactLayoutCache::with_capacity(node_count);
32
33        for i in 0..node_count {
34            let node_id = NodeId::new(i);
35            let nd = &node_data[i];
36
37            // =====================================================================
38            // Tier 1: Encode all 20 enum properties into u64
39            // =====================================================================
40
41            let display = self
42                .get_display(nd, &node_id, &default_state)
43                .and_then(|v| v.get_property().copied())
44                .unwrap_or_default();
45            let position = self
46                .get_position(nd, &node_id, &default_state)
47                .and_then(|v| v.get_property().copied())
48                .unwrap_or_default();
49            let float = self
50                .get_float(nd, &node_id, &default_state)
51                .and_then(|v| v.get_property().copied())
52                .unwrap_or_default();
53            let overflow_x = self
54                .get_overflow_x(nd, &node_id, &default_state)
55                .and_then(|v| v.get_property().copied())
56                .unwrap_or_default();
57            let overflow_y = self
58                .get_overflow_y(nd, &node_id, &default_state)
59                .and_then(|v| v.get_property().copied())
60                .unwrap_or_default();
61            let box_sizing = self
62                .get_box_sizing(nd, &node_id, &default_state)
63                .and_then(|v| v.get_property().copied())
64                .unwrap_or_default();
65            let flex_direction = self
66                .get_flex_direction(nd, &node_id, &default_state)
67                .and_then(|v| v.get_property().copied())
68                .unwrap_or_default();
69            let flex_wrap = self
70                .get_flex_wrap(nd, &node_id, &default_state)
71                .and_then(|v| v.get_property().copied())
72                .unwrap_or_default();
73            let justify_content = self
74                .get_justify_content(nd, &node_id, &default_state)
75                .and_then(|v| v.get_property().copied())
76                .unwrap_or_default();
77            let align_items = self
78                .get_align_items(nd, &node_id, &default_state)
79                .and_then(|v| v.get_property().copied())
80                .unwrap_or_default();
81            let align_content = self
82                .get_align_content(nd, &node_id, &default_state)
83                .and_then(|v| v.get_property().copied())
84                .unwrap_or_default();
85            let writing_mode = self
86                .get_writing_mode(nd, &node_id, &default_state)
87                .and_then(|v| v.get_property().copied())
88                .unwrap_or_default();
89            let clear = self
90                .get_clear(nd, &node_id, &default_state)
91                .and_then(|v| v.get_property().copied())
92                .unwrap_or_default();
93            let font_weight = self
94                .get_font_weight(nd, &node_id, &default_state)
95                .and_then(|v| v.get_property().copied())
96                .unwrap_or_default();
97            let font_style = self
98                .get_font_style(nd, &node_id, &default_state)
99                .and_then(|v| v.get_property().copied())
100                .unwrap_or_default();
101            let text_align = self
102                .get_text_align(nd, &node_id, &default_state)
103                .and_then(|v| v.get_property().copied())
104                .unwrap_or_default();
105            let visibility = self
106                .get_visibility(nd, &node_id, &default_state)
107                .and_then(|v| v.get_property().copied())
108                .unwrap_or_default();
109            let white_space = self
110                .get_white_space(nd, &node_id, &default_state)
111                .and_then(|v| v.get_property().copied())
112                .unwrap_or_default();
113            let direction = self
114                .get_direction(nd, &node_id, &default_state)
115                .and_then(|v| v.get_property().copied())
116                .unwrap_or_default();
117            let vertical_align = self
118                .get_vertical_align(nd, &node_id, &default_state)
119                .and_then(|v| v.get_property().copied())
120                .unwrap_or_default();
121
122            let border_collapse = self
123                .get_border_collapse(nd, &node_id, &default_state)
124                .and_then(|v| v.get_property().copied())
125                .unwrap_or_default();
126
127            result.tier1_enums[i] = encode_tier1(
128                display,
129                position,
130                float,
131                overflow_x,
132                overflow_y,
133                box_sizing,
134                flex_direction,
135                flex_wrap,
136                justify_content,
137                align_items,
138                align_content,
139                writing_mode,
140                clear,
141                font_weight,
142                font_style,
143                text_align,
144                visibility,
145                white_space,
146                direction,
147                vertical_align,
148                border_collapse,
149            );
150
151            // =====================================================================
152            // Tier 2: Encode numeric dimension properties
153            // =====================================================================
154
155            // Width/Height are enums: Auto | Px(PixelValue) | MinContent | MaxContent | Calc
156            if let Some(val) = self.get_width(nd, &node_id, &default_state) {
157                result.tier2_dims[i].width = encode_layout_width(val);
158            }
159            if let Some(val) = self.get_height(nd, &node_id, &default_state) {
160                result.tier2_dims[i].height = encode_layout_height(val);
161            }
162
163            // Min/Max Width/Height are simple PixelValue wrappers
164            if let Some(val) = self.get_min_width(nd, &node_id, &default_state) {
165                result.tier2_dims[i].min_width = encode_pixel_prop(val);
166            }
167            if let Some(val) = self.get_max_width(nd, &node_id, &default_state) {
168                result.tier2_dims[i].max_width = encode_pixel_prop(val);
169            }
170            if let Some(val) = self.get_min_height(nd, &node_id, &default_state) {
171                result.tier2_dims[i].min_height = encode_pixel_prop(val);
172            }
173            if let Some(val) = self.get_max_height(nd, &node_id, &default_state) {
174                result.tier2_dims[i].max_height = encode_pixel_prop(val);
175            }
176
177            // Flex basis (enum: Auto | Exact(PixelValue))
178            if let Some(val) = self.get_flex_basis(nd, &node_id, &default_state) {
179                result.tier2_dims[i].flex_basis = encode_flex_basis(val);
180            }
181
182            // Font size
183            if let Some(val) = self.get_font_size(nd, &node_id, &default_state) {
184                result.tier2_dims[i].font_size = encode_pixel_prop(val);
185            }
186
187            // Padding (i16 × 10 resolved px)
188            if let Some(val) = self.get_padding_top(nd, &node_id, &default_state) {
189                result.tier2_dims[i].padding_top = encode_css_pixel_as_i16(val);
190            }
191            if let Some(val) = self.get_padding_right(nd, &node_id, &default_state) {
192                result.tier2_dims[i].padding_right = encode_css_pixel_as_i16(val);
193            }
194            if let Some(val) = self.get_padding_bottom(nd, &node_id, &default_state) {
195                result.tier2_dims[i].padding_bottom = encode_css_pixel_as_i16(val);
196            }
197            if let Some(val) = self.get_padding_left(nd, &node_id, &default_state) {
198                result.tier2_dims[i].padding_left = encode_css_pixel_as_i16(val);
199            }
200
201            // Margin (i16, auto is special)
202            if let Some(val) = self.get_margin_top(nd, &node_id, &default_state) {
203                result.tier2_dims[i].margin_top = encode_margin_i16(val);
204            }
205            if let Some(val) = self.get_margin_right(nd, &node_id, &default_state) {
206                result.tier2_dims[i].margin_right = encode_margin_i16(val);
207            }
208            if let Some(val) = self.get_margin_bottom(nd, &node_id, &default_state) {
209                result.tier2_dims[i].margin_bottom = encode_margin_i16(val);
210            }
211            if let Some(val) = self.get_margin_left(nd, &node_id, &default_state) {
212                result.tier2_dims[i].margin_left = encode_margin_i16(val);
213            }
214
215            // Border widths (i16 × 10 resolved px)
216            if let Some(val) = self.get_border_top_width(nd, &node_id, &default_state) {
217                result.tier2_dims[i].border_top_width = encode_css_pixel_as_i16(val);
218            }
219            if let Some(val) = self.get_border_right_width(nd, &node_id, &default_state) {
220                result.tier2_dims[i].border_right_width = encode_css_pixel_as_i16(val);
221            }
222            if let Some(val) = self.get_border_bottom_width(nd, &node_id, &default_state) {
223                result.tier2_dims[i].border_bottom_width = encode_css_pixel_as_i16(val);
224            }
225            if let Some(val) = self.get_border_left_width(nd, &node_id, &default_state) {
226                result.tier2_dims[i].border_left_width = encode_css_pixel_as_i16(val);
227            }
228
229            // Position offsets (top/right/bottom/left)
230            if let Some(val) = self.get_top(nd, &node_id, &default_state) {
231                result.tier2_dims[i].top = encode_css_pixel_as_i16(val);
232            }
233            if let Some(val) = self.get_right(nd, &node_id, &default_state) {
234                result.tier2_dims[i].right = encode_css_pixel_as_i16(val);
235            }
236            if let Some(val) = self.get_bottom(nd, &node_id, &default_state) {
237                result.tier2_dims[i].bottom = encode_css_pixel_as_i16(val);
238            }
239            if let Some(val) = self.get_left(nd, &node_id, &default_state) {
240                result.tier2_dims[i].left = encode_css_pixel_as_i16(val);
241            }
242
243            // Flex grow/shrink (u16 × 100)
244            if let Some(val) = self.get_flex_grow(nd, &node_id, &default_state) {
245                if let Some(exact) = val.get_property() {
246                    result.tier2_dims[i].flex_grow = encode_flex_u16(exact.inner.get());
247                }
248            }
249            if let Some(val) = self.get_flex_shrink(nd, &node_id, &default_state) {
250                if let Some(exact) = val.get_property() {
251                    result.tier2_dims[i].flex_shrink = encode_flex_u16(exact.inner.get());
252                }
253            }
254
255            // Z-index
256            if let Some(val) = self.get_z_index(nd, &node_id, &default_state) {
257                if let Some(exact) = val.get_property() {
258                    match exact {
259                        LayoutZIndex::Auto => result.tier2_dims[i].z_index = I16_AUTO,
260                        LayoutZIndex::Integer(z) => {
261                            if *z >= I16_SENTINEL_THRESHOLD as i32 {
262                                result.tier2_dims[i].z_index = I16_SENTINEL;
263                            } else {
264                                result.tier2_dims[i].z_index = *z as i16;
265                            }
266                        }
267                    }
268                }
269            }
270
271            // Border styles (packed into u16)
272            {
273                let bts = self.get_border_top_style(nd, &node_id, &default_state)
274                    .and_then(|v| v.get_property().copied())
275                    .map(|v| v.inner)
276                    .unwrap_or_default();
277                let brs = self.get_border_right_style(nd, &node_id, &default_state)
278                    .and_then(|v| v.get_property().copied())
279                    .map(|v| v.inner)
280                    .unwrap_or_default();
281                let bbs = self.get_border_bottom_style(nd, &node_id, &default_state)
282                    .and_then(|v| v.get_property().copied())
283                    .map(|v| v.inner)
284                    .unwrap_or_default();
285                let bls = self.get_border_left_style(nd, &node_id, &default_state)
286                    .and_then(|v| v.get_property().copied())
287                    .map(|v| v.inner)
288                    .unwrap_or_default();
289                result.tier2_dims[i].border_styles_packed =
290                    encode_border_styles_packed(bts, brs, bbs, bls);
291            }
292
293            // Border colors (ColorU → u32 as 0xRRGGBBAA)
294            if let Some(val) = self.get_border_top_color(nd, &node_id, &default_state) {
295                if let Some(color) = val.get_property() {
296                    result.tier2_dims[i].border_top_color = encode_color_u32(&color.inner);
297                }
298            }
299            if let Some(val) = self.get_border_right_color(nd, &node_id, &default_state) {
300                if let Some(color) = val.get_property() {
301                    result.tier2_dims[i].border_right_color = encode_color_u32(&color.inner);
302                }
303            }
304            if let Some(val) = self.get_border_bottom_color(nd, &node_id, &default_state) {
305                if let Some(color) = val.get_property() {
306                    result.tier2_dims[i].border_bottom_color = encode_color_u32(&color.inner);
307                }
308            }
309            if let Some(val) = self.get_border_left_color(nd, &node_id, &default_state) {
310                if let Some(color) = val.get_property() {
311                    result.tier2_dims[i].border_left_color = encode_color_u32(&color.inner);
312                }
313            }
314
315            // Border spacing (two PixelValue → i16 × 10 resolved px)
316            if let Some(val) = self.get_border_spacing(nd, &node_id, &default_state) {
317                if let Some(spacing) = val.get_property() {
318                    if spacing.horizontal.metric == SizeMetric::Px {
319                        result.tier2_dims[i].border_spacing_h = encode_resolved_px_i16(spacing.horizontal.number.get());
320                    }
321                    if spacing.vertical.metric == SizeMetric::Px {
322                        result.tier2_dims[i].border_spacing_v = encode_resolved_px_i16(spacing.vertical.number.get());
323                    }
324                }
325            }
326
327            // Tab size (PixelValue → i16 × 10 resolved px)
328            if let Some(val) = self.get_tab_size(nd, &node_id, &default_state) {
329                result.tier2_dims[i].tab_size = encode_css_pixel_as_i16(val);
330            }
331
332            // =====================================================================
333            // Tier 2b: Text properties
334            // =====================================================================
335
336            // Text color (ColorU → u32 as 0xRRGGBBAA)
337            if let Some(val) = self.get_text_color(nd, &node_id, &default_state) {
338                if let Some(color) = val.get_property() {
339                    let c = &color.inner;
340                    result.tier2b_text[i].text_color =
341                        ((c.r as u32) << 24) | ((c.g as u32) << 16) | ((c.b as u32) << 8) | (c.a as u32);
342                }
343            }
344
345            // Font-family (hash the whole StyleFontFamilyVec for fast comparison)
346            if let Some(val) = self.get_font_family(nd, &node_id, &default_state) {
347                if let Some(families) = val.get_property() {
348                    let mut hasher = DefaultHasher::new();
349                    families.hash(&mut hasher);
350                    let h = hasher.finish();
351                    // 0 is reserved as "unset" sentinel, avoid collision
352                    result.tier2b_text[i].font_family_hash = if h == 0 { 1 } else { h };
353                }
354            }
355
356            // Line-height (PercentageValue: internal number is value × 1000, we store % × 10)
357            if let Some(val) = self.get_line_height(nd, &node_id, &default_state) {
358                if let Some(lh) = val.get_property() {
359                    // lh.inner is PercentageValue, normalized() = value/100.
360                    // Internal number = percentage × 1000 (e.g. 120% → 120_000).
361                    // We store percentage × 10 as i16 (e.g. 120% → 1200).
362                    let pct_x10 = (lh.inner.normalized() * 1000.0).round() as i32;
363                    if pct_x10 >= -32768 && pct_x10 < I16_SENTINEL_THRESHOLD as i32 {
364                        result.tier2b_text[i].line_height = pct_x10 as i16;
365                    } else {
366                        result.tier2b_text[i].line_height = I16_SENTINEL;
367                    }
368                }
369            }
370
371            // Letter-spacing (PixelValue wrapper → i16 × 10 resolved px)
372            if let Some(val) = self.get_letter_spacing(nd, &node_id, &default_state) {
373                result.tier2b_text[i].letter_spacing = encode_css_pixel_as_i16(val);
374            }
375
376            // Word-spacing (PixelValue wrapper → i16 × 10 resolved px)
377            if let Some(val) = self.get_word_spacing(nd, &node_id, &default_state) {
378                result.tier2b_text[i].word_spacing = encode_css_pixel_as_i16(val);
379            }
380
381            // Text-indent (PixelValue wrapper → i16 × 10 resolved px)
382            if let Some(val) = self.get_text_indent(nd, &node_id, &default_state) {
383                result.tier2b_text[i].text_indent = encode_css_pixel_as_i16(val);
384            }
385        }
386
387        result
388    }
389}
390
391// =============================================================================
392// Helper encoders for dimension properties
393// =============================================================================
394
395/// Encode a CssPropertyValue<LayoutWidth> into u32 compact form.
396fn encode_layout_width<T: LayoutWidthLike>(val: &CssPropertyValue<T>) -> u32 {
397    match val {
398        CssPropertyValue::Exact(w) => w.encode_compact_u32(),
399        CssPropertyValue::Auto => U32_AUTO,
400        CssPropertyValue::Initial => U32_INITIAL,
401        CssPropertyValue::Inherit => U32_INHERIT,
402        CssPropertyValue::None => U32_NONE,
403        _ => U32_SENTINEL,
404    }
405}
406
407/// Encode a CssPropertyValue<LayoutHeight> into u32 compact form.
408fn encode_layout_height<T: LayoutWidthLike>(val: &CssPropertyValue<T>) -> u32 {
409    encode_layout_width(val)
410}
411
412/// Trait for types that can be encoded as compact u32 dimension values.
413/// Implemented for LayoutWidth, LayoutHeight (which are Auto|Px|MinContent|MaxContent|Calc enums).
414trait LayoutWidthLike {
415    fn encode_compact_u32(&self) -> u32;
416}
417
418impl LayoutWidthLike for LayoutWidth {
419    fn encode_compact_u32(&self) -> u32 {
420        match self {
421            LayoutWidth::Auto => U32_AUTO,
422            LayoutWidth::Px(pv) => encode_pixel_value_u32(pv),
423            LayoutWidth::MinContent => U32_MIN_CONTENT,
424            LayoutWidth::MaxContent => U32_MAX_CONTENT,
425            LayoutWidth::Calc(_) => U32_SENTINEL, // Calc → overflow to tier 3
426        }
427    }
428}
429
430impl LayoutWidthLike for LayoutHeight {
431    fn encode_compact_u32(&self) -> u32 {
432        match self {
433            LayoutHeight::Auto => U32_AUTO,
434            LayoutHeight::Px(pv) => encode_pixel_value_u32(pv),
435            LayoutHeight::MinContent => U32_MIN_CONTENT,
436            LayoutHeight::MaxContent => U32_MAX_CONTENT,
437            LayoutHeight::Calc(_) => U32_SENTINEL,
438        }
439    }
440}
441
442/// Encode a CssPropertyValue wrapping a simple PixelValue struct (LayoutMinWidth, etc.)
443fn encode_pixel_prop<T: HasInnerPixelValue>(val: &CssPropertyValue<T>) -> u32 {
444    match val {
445        CssPropertyValue::Exact(inner) => encode_pixel_value_u32(&inner.get_inner_pixel()),
446        CssPropertyValue::Auto => U32_AUTO,
447        CssPropertyValue::Initial => U32_INITIAL,
448        CssPropertyValue::Inherit => U32_INHERIT,
449        CssPropertyValue::None => U32_NONE,
450        _ => U32_SENTINEL,
451    }
452}
453
454/// Trait for dimension structs wrapping `inner: PixelValue`.
455trait HasInnerPixelValue {
456    fn get_inner_pixel(&self) -> azul_css::props::basic::pixel::PixelValue;
457}
458
459macro_rules! impl_has_inner_pixel {
460    ($($ty:ty),*) => {
461        $(
462            impl HasInnerPixelValue for $ty {
463                fn get_inner_pixel(&self) -> azul_css::props::basic::pixel::PixelValue {
464                    self.inner
465                }
466            }
467        )*
468    };
469}
470
471impl_has_inner_pixel!(
472    azul_css::props::layout::dimensions::LayoutMinWidth,
473    azul_css::props::layout::dimensions::LayoutMaxWidth,
474    azul_css::props::layout::dimensions::LayoutMinHeight,
475    azul_css::props::layout::dimensions::LayoutMaxHeight,
476    azul_css::props::basic::font::StyleFontSize,
477    azul_css::props::layout::spacing::LayoutPaddingTop,
478    azul_css::props::layout::spacing::LayoutPaddingRight,
479    azul_css::props::layout::spacing::LayoutPaddingBottom,
480    azul_css::props::layout::spacing::LayoutPaddingLeft,
481    azul_css::props::layout::spacing::LayoutMarginTop,
482    azul_css::props::layout::spacing::LayoutMarginRight,
483    azul_css::props::layout::spacing::LayoutMarginBottom,
484    azul_css::props::layout::spacing::LayoutMarginLeft,
485    azul_css::props::style::border::LayoutBorderTopWidth,
486    azul_css::props::style::border::LayoutBorderRightWidth,
487    azul_css::props::style::border::LayoutBorderBottomWidth,
488    azul_css::props::style::border::LayoutBorderLeftWidth,
489    azul_css::props::layout::position::LayoutTop,
490    azul_css::props::layout::position::LayoutRight,
491    azul_css::props::layout::position::LayoutInsetBottom,
492    azul_css::props::layout::position::LayoutLeft,
493    azul_css::props::style::text::StyleLetterSpacing,
494    azul_css::props::style::text::StyleWordSpacing,
495    azul_css::props::style::text::StyleTextIndent,
496    azul_css::props::style::text::StyleTabSize
497);
498
499/// Encode a CssPropertyValue<T> where T wraps a PixelValue, as i16 (×10 resolved px).
500/// Only encodes absolute `px` values; everything else → sentinel.
501fn encode_css_pixel_as_i16<T: HasInnerPixelValue>(val: &CssPropertyValue<T>) -> i16 {
502    match val {
503        CssPropertyValue::Exact(inner) => {
504            let pv = inner.get_inner_pixel();
505            if pv.metric == SizeMetric::Px {
506                encode_resolved_px_i16(pv.number.get())
507            } else {
508                I16_SENTINEL // non-px units need resolution context → slow path
509            }
510        }
511        CssPropertyValue::Auto => I16_AUTO,
512        CssPropertyValue::Initial => I16_INITIAL,
513        CssPropertyValue::Inherit => I16_INHERIT,
514        _ => I16_SENTINEL,
515    }
516}
517
518/// Encode margin: same as encode_css_pixel_as_i16 but Auto is a distinct value.
519fn encode_margin_i16<T: HasInnerPixelValue>(val: &CssPropertyValue<T>) -> i16 {
520    encode_css_pixel_as_i16(val)
521}
522
523/// Encode CssPropertyValue<LayoutFlexBasis> — LayoutFlexBasis is Auto | Exact(PixelValue).
524fn encode_flex_basis(val: &CssPropertyValue<LayoutFlexBasis>) -> u32 {
525    match val {
526        CssPropertyValue::Exact(fb) => match fb {
527            LayoutFlexBasis::Auto => U32_AUTO,
528            LayoutFlexBasis::Exact(pv) => encode_pixel_value_u32(pv),
529        },
530        CssPropertyValue::Auto => U32_AUTO,
531        CssPropertyValue::Initial => U32_INITIAL,
532        CssPropertyValue::Inherit => U32_INHERIT,
533        CssPropertyValue::None => U32_NONE,
534        _ => U32_SENTINEL,
535    }
536}