1extern crate alloc;
25
26use alloc::{boxed::Box, string::String, vec::Vec};
27use core::fmt::Write;
28use core::mem::ManuallyDrop;
29
30use crate::dom::NodeType;
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
35pub enum CssPropertyOrigin {
36 Inherited,
38 Own,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
44pub struct CssPropertyWithOrigin {
45 pub property: CssProperty,
46 pub origin: CssPropertyOrigin,
47}
48
49use azul_css::{
50 css::{Css, CssPath},
51 props::{
52 basic::{StyleFontFamily, StyleFontFamilyVec, StyleFontSize},
53 layout::{LayoutDisplay, LayoutHeight, LayoutWidth},
54 property::{
55 BoxDecorationBreakValue, BreakInsideValue, CaretAnimationDurationValue,
56 CaretColorValue, CaretWidthValue, ClipPathValue, ColumnCountValue, ColumnFillValue,
57 ColumnRuleColorValue, ColumnRuleStyleValue, ColumnRuleWidthValue, ColumnSpanValue,
58 ColumnWidthValue, ContentValue, CounterIncrementValue, CounterResetValue, CssProperty,
59 CssPropertyType, FlowFromValue, FlowIntoValue, LayoutAlignContentValue,
60 LayoutAlignItemsValue, LayoutAlignSelfValue, LayoutBorderBottomWidthValue,
61 LayoutBorderLeftWidthValue, LayoutBorderRightWidthValue, LayoutBorderSpacingValue,
62 LayoutBorderTopWidthValue, LayoutBoxSizingValue, LayoutClearValue,
63 LayoutColumnGapValue, LayoutDisplayValue, LayoutFlexBasisValue,
64 LayoutFlexDirectionValue, LayoutFlexGrowValue, LayoutFlexShrinkValue,
65 LayoutFlexWrapValue, LayoutFloatValue, LayoutGapValue, LayoutGridAutoColumnsValue,
66 LayoutGridAutoFlowValue, LayoutGridAutoRowsValue, LayoutGridColumnValue,
67 LayoutGridRowValue, LayoutGridTemplateColumnsValue, LayoutGridTemplateRowsValue,
68 LayoutHeightValue, LayoutInsetBottomValue, LayoutJustifyContentValue,
69 LayoutJustifyItemsValue, LayoutJustifySelfValue, LayoutLeftValue,
70 LayoutMarginBottomValue, LayoutMarginLeftValue, LayoutMarginRightValue,
71 LayoutMarginTopValue, LayoutMaxHeightValue, LayoutMaxWidthValue, LayoutMinHeightValue,
72 LayoutMinWidthValue, LayoutOverflowValue, LayoutPaddingBottomValue,
73 LayoutPaddingLeftValue, LayoutPaddingRightValue, LayoutPaddingTopValue,
74 LayoutPositionValue, LayoutRightValue, LayoutRowGapValue, LayoutScrollbarWidthValue,
75 LayoutTableLayoutValue, LayoutTextJustifyValue, LayoutTopValue, LayoutWidthValue,
76 LayoutWritingModeValue, LayoutZIndexValue, OrphansValue, PageBreakValue,
77 StyleBackgroundContentValue, ScrollbarFadeDelayValue, ScrollbarFadeDurationValue,
78 ScrollbarVisibilityModeValue, SelectionBackgroundColorValue, SelectionColorValue,
79 SelectionRadiusValue, ShapeImageThresholdValue, ShapeInsideValue, ShapeMarginValue,
80 ShapeOutsideValue, StringSetValue, StyleBackfaceVisibilityValue,
81 StyleBackgroundContentVecValue, StyleBackgroundPositionVecValue,
82 StyleBackgroundRepeatVecValue, StyleBackgroundSizeVecValue,
83 StyleBorderBottomColorValue, StyleBorderBottomLeftRadiusValue,
84 StyleBorderBottomRightRadiusValue, StyleBorderBottomStyleValue,
85 StyleBorderCollapseValue, StyleBorderLeftColorValue, StyleBorderLeftStyleValue,
86 StyleBorderRightColorValue, StyleBorderRightStyleValue, StyleBorderTopColorValue,
87 StyleBorderTopLeftRadiusValue, StyleBorderTopRightRadiusValue,
88 StyleBorderTopStyleValue, StyleBoxShadowValue, StyleCaptionSideValue, StyleCursorValue,
89 StyleDirectionValue, StyleEmptyCellsValue, StyleExclusionMarginValue,
90 StyleFilterVecValue, StyleFontFamilyVecValue, StyleFontSizeValue, StyleFontStyleValue,
91 StyleFontValue, StyleFontWeightValue, StyleHangingPunctuationValue,
92 StyleHyphenationLanguageValue, StyleHyphensValue, StyleInitialLetterValue,
93 StyleLetterSpacingValue, StyleLineBreakValue, StyleLineClampValue, StyleLineHeightValue,
94 StyleListStylePositionValue, StyleListStyleTypeValue, StyleMixBlendModeValue,
95 StyleAspectRatioValue, StyleObjectFitValue, StyleObjectPositionValue,
96 StyleOpacityValue, StylePerspectiveOriginValue,
97 StyleScrollbarColorValue, StyleOverflowWrapValue, StyleTabSizeValue,
98 StyleTextAlignLastValue, StyleTextOrientationValue, StyleTextTransformValue,
99 StyleTextAlignValue, StyleTextColorValue,
100 StyleTextCombineUprightValue, StyleUnicodeBidiValue,
101 StyleTextBoxTrimValue, StyleTextBoxEdgeValue,
102 StyleDominantBaselineValue, StyleAlignmentBaselineValue,
103 StyleInitialLetterAlignValue, StyleInitialLetterWrapValue,
104 StyleScrollbarGutterValue, StyleOverflowClipMarginValue, StyleClipRectValue,
105 StyleTextDecorationValue, StyleTextIndentValue,
106 StyleTransformOriginValue, StyleTransformVecValue, StyleUserSelectValue,
107 StyleVerticalAlignValue, StyleVisibilityValue, StyleWhiteSpaceValue,
108 StyleWordBreakValue, StyleWordSpacingValue, WidowsValue,
109 },
110 style::{StyleCursor, StyleTextColor, StyleTransformOrigin},
111 },
112 AzString,
113};
114
115use crate::{
116 dom::{NodeData, NodeId, TabIndex, TagId},
117 id::{NodeDataContainer, NodeDataContainerRef},
118 style::CascadeInfo,
119 styled_dom::{
120 NodeHierarchyItem, NodeHierarchyItemId, NodeHierarchyItemVec, ParentWithNodeDepth,
121 ParentWithNodeDepthVec, StyledNodeState, TagIdToNodeIdMapping,
122 },
123};
124
125use azul_css::dynamic_selector::{
126 CssPropertyWithConditions, CssPropertyWithConditionsVec, DynamicSelectorContext,
127};
128
129#[cfg(feature = "std")]
130std::thread_local! {
131 static PROP_COUNTS: core::cell::RefCell<
132 std::collections::HashMap<&'static str, usize>
133 > = core::cell::RefCell::new(std::collections::HashMap::new());
134}
135
136#[cfg(feature = "std")]
145#[must_use] pub fn drain_css_prop_counts() -> Vec<(&'static str, usize)> {
146 PROP_COUNTS
149 .try_with(|c| {
150 let map = core::mem::take(&mut *c.borrow_mut());
151 let mut v: Vec<_> = map.into_iter().collect();
152 v.sort_by(|a, b| b.1.cmp(&a.1));
153 v
154 })
155 .unwrap_or_default()
156}
157
158const PT_TO_PX: f32 = 1.333_333;
160const IN_TO_PX: f32 = 96.0;
161const CM_TO_PX: f32 = 37.795_277;
162const MM_TO_PX: f32 = 3.779_527_7;
163
164#[allow(unused_macros)]
166macro_rules! match_property_value {
167 ($property:expr, $value:ident, $expr:expr) => {
168 match $property {
169 CssProperty::CaretColor($value) => $expr,
170 CssProperty::CaretAnimationDuration($value) => $expr,
171 CssProperty::SelectionBackgroundColor($value) => $expr,
172 CssProperty::SelectionColor($value) => $expr,
173 CssProperty::SelectionRadius($value) => $expr,
174 CssProperty::TextColor($value) => $expr,
175 CssProperty::FontSize($value) => $expr,
176 CssProperty::FontFamily($value) => $expr,
177 CssProperty::FontWeight($value) => $expr,
178 CssProperty::FontStyle($value) => $expr,
179 CssProperty::TextAlign($value) => $expr,
180 CssProperty::TextJustify($value) => $expr,
181 CssProperty::VerticalAlign($value) => $expr,
182 CssProperty::LetterSpacing($value) => $expr,
183 CssProperty::TextIndent($value) => $expr,
184 CssProperty::InitialLetter($value) => $expr,
185 CssProperty::LineClamp($value) => $expr,
186 CssProperty::HangingPunctuation($value) => $expr,
187 CssProperty::TextCombineUpright($value) => $expr,
188 CssProperty::UnicodeBidi($value) => $expr,
189 CssProperty::TextBoxTrim($value) => $expr,
190 CssProperty::TextBoxEdge($value) => $expr,
191 CssProperty::DominantBaseline($value) => $expr,
192 CssProperty::AlignmentBaseline($value) => $expr,
193 CssProperty::InitialLetterAlign($value) => $expr,
194 CssProperty::InitialLetterWrap($value) => $expr,
195 CssProperty::ScrollbarGutter($value) => $expr,
196 CssProperty::OverflowClipMargin($value) => $expr,
197 CssProperty::Clip($value) => $expr,
198 CssProperty::ExclusionMargin($value) => $expr,
199 CssProperty::HyphenationLanguage($value) => $expr,
200 CssProperty::LineHeight($value) => $expr,
201 CssProperty::WordSpacing($value) => $expr,
202 CssProperty::TabSize($value) => $expr,
203 CssProperty::WhiteSpace($value) => $expr,
204 CssProperty::Hyphens($value) => $expr,
205 CssProperty::Direction($value) => $expr,
206 CssProperty::UserSelect($value) => $expr,
207 CssProperty::TextDecoration($value) => $expr,
208 CssProperty::Cursor($value) => $expr,
209 CssProperty::Display($value) => $expr,
210 CssProperty::Float($value) => $expr,
211 CssProperty::BoxSizing($value) => $expr,
212 CssProperty::Width($value) => $expr,
213 CssProperty::Height($value) => $expr,
214 CssProperty::MinWidth($value) => $expr,
215 CssProperty::MinHeight($value) => $expr,
216 CssProperty::MaxWidth($value) => $expr,
217 CssProperty::MaxHeight($value) => $expr,
218 CssProperty::Position($value) => $expr,
219 CssProperty::Top($value) => $expr,
220 CssProperty::Right($value) => $expr,
221 CssProperty::Left($value) => $expr,
222 CssProperty::Bottom($value) => $expr,
223 CssProperty::ZIndex($value) => $expr,
224 CssProperty::FlexWrap($value) => $expr,
225 CssProperty::FlexDirection($value) => $expr,
226 CssProperty::FlexGrow($value) => $expr,
227 CssProperty::FlexShrink($value) => $expr,
228 CssProperty::FlexBasis($value) => $expr,
229 CssProperty::JustifyContent($value) => $expr,
230 CssProperty::AlignItems($value) => $expr,
231 CssProperty::AlignContent($value) => $expr,
232 CssProperty::AlignSelf($value) => $expr,
233 CssProperty::JustifyItems($value) => $expr,
234 CssProperty::JustifySelf($value) => $expr,
235 CssProperty::BackgroundContent($value) => $expr,
236 CssProperty::BackgroundPosition($value) => $expr,
237 CssProperty::BackgroundSize($value) => $expr,
238 CssProperty::BackgroundRepeat($value) => $expr,
239 CssProperty::OverflowX($value) => $expr,
240 CssProperty::OverflowY($value) => $expr,
241 CssProperty::OverflowBlock($value) => $expr,
242 CssProperty::OverflowInline($value) => $expr,
243 CssProperty::PaddingTop($value) => $expr,
244 CssProperty::PaddingLeft($value) => $expr,
245 CssProperty::PaddingRight($value) => $expr,
246 CssProperty::PaddingBottom($value) => $expr,
247 CssProperty::MarginTop($value) => $expr,
248 CssProperty::MarginLeft($value) => $expr,
249 CssProperty::MarginRight($value) => $expr,
250 CssProperty::MarginBottom($value) => $expr,
251 CssProperty::BorderTopLeftRadius($value) => $expr,
252 CssProperty::BorderTopRightRadius($value) => $expr,
253 CssProperty::BorderBottomLeftRadius($value) => $expr,
254 CssProperty::BorderBottomRightRadius($value) => $expr,
255 CssProperty::BorderTopColor($value) => $expr,
256 CssProperty::BorderRightColor($value) => $expr,
257 CssProperty::BorderLeftColor($value) => $expr,
258 CssProperty::BorderBottomColor($value) => $expr,
259 CssProperty::BorderTopStyle($value) => $expr,
260 CssProperty::BorderRightStyle($value) => $expr,
261 CssProperty::BorderLeftStyle($value) => $expr,
262 CssProperty::BorderBottomStyle($value) => $expr,
263 CssProperty::BorderTopWidth($value) => $expr,
264 CssProperty::BorderRightWidth($value) => $expr,
265 CssProperty::BorderLeftWidth($value) => $expr,
266 CssProperty::BorderBottomWidth($value) => $expr,
267 CssProperty::BoxShadow($value) => $expr,
268 CssProperty::Opacity($value) => $expr,
269 CssProperty::Transform($value) => $expr,
270 CssProperty::TransformOrigin($value) => $expr,
271 CssProperty::PerspectiveOrigin($value) => $expr,
272 CssProperty::BackfaceVisibility($value) => $expr,
273 CssProperty::MixBlendMode($value) => $expr,
274 CssProperty::Filter($value) => $expr,
275 CssProperty::Visibility($value) => $expr,
276 CssProperty::WritingMode($value) => $expr,
277 CssProperty::GridTemplateColumns($value) => $expr,
278 CssProperty::GridTemplateRows($value) => $expr,
279 CssProperty::GridAutoColumns($value) => $expr,
280 CssProperty::GridAutoRows($value) => $expr,
281 CssProperty::GridAutoFlow($value) => $expr,
282 CssProperty::GridColumn($value) => $expr,
283 CssProperty::GridRow($value) => $expr,
284 CssProperty::GridTemplateAreas($value) => $expr,
285 CssProperty::Gap($value) => $expr,
286 CssProperty::ColumnGap($value) => $expr,
287 CssProperty::RowGap($value) => $expr,
288 CssProperty::Clear($value) => $expr,
289 CssProperty::ScrollbarTrack($value) => $expr,
290 CssProperty::ScrollbarThumb($value) => $expr,
291 CssProperty::ScrollbarButton($value) => $expr,
292 CssProperty::ScrollbarCorner($value) => $expr,
293 CssProperty::ScrollbarResizer($value) => $expr,
294 CssProperty::ScrollbarWidth($value) => $expr,
295 CssProperty::ScrollbarColor($value) => $expr,
296 CssProperty::ListStyleType($value) => $expr,
297 CssProperty::ListStylePosition($value) => $expr,
298 CssProperty::Font($value) => $expr,
299 CssProperty::ColumnCount($value) => $expr,
300 CssProperty::ColumnWidth($value) => $expr,
301 CssProperty::ColumnSpan($value) => $expr,
302 CssProperty::ColumnFill($value) => $expr,
303 CssProperty::ColumnRuleStyle($value) => $expr,
304 CssProperty::ColumnRuleWidth($value) => $expr,
305 CssProperty::ColumnRuleColor($value) => $expr,
306 CssProperty::FlowInto($value) => $expr,
307 CssProperty::FlowFrom($value) => $expr,
308 CssProperty::ShapeOutside($value) => $expr,
309 CssProperty::ShapeInside($value) => $expr,
310 CssProperty::ShapeImageThreshold($value) => $expr,
311 CssProperty::ShapeMargin($value) => $expr,
312 CssProperty::ClipPath($value) => $expr,
313 CssProperty::Content($value) => $expr,
314 CssProperty::CounterIncrement($value) => $expr,
315 CssProperty::CounterReset($value) => $expr,
316 CssProperty::StringSet($value) => $expr,
317 CssProperty::Orphans($value) => $expr,
318 CssProperty::Widows($value) => $expr,
319 CssProperty::PageBreakBefore($value) => $expr,
320 CssProperty::PageBreakAfter($value) => $expr,
321 CssProperty::PageBreakInside($value) => $expr,
322 CssProperty::BreakInside($value) => $expr,
323 CssProperty::BoxDecorationBreak($value) => $expr,
324 CssProperty::TableLayout($value) => $expr,
325 CssProperty::BorderCollapse($value) => $expr,
326 CssProperty::BorderSpacing($value) => $expr,
327 CssProperty::CaptionSide($value) => $expr,
328 CssProperty::EmptyCells($value) => $expr,
329 }
330 };
331}
332
333#[derive(Debug, Clone, PartialEq, Eq)]
339pub struct StatefulCssProperty {
340 pub state: azul_css::dynamic_selector::PseudoStateType,
341 pub prop_type: CssPropertyType,
342 pub property: CssProperty,
343}
344
345#[derive(Debug, Clone)]
364pub struct FlatVecVec<T> {
365 build: Vec<Vec<T>>,
367 data: Vec<T>,
369 offsets: Vec<(u32, u32)>,
371}
372
373impl<T: PartialEq> PartialEq for FlatVecVec<T> {
374 fn eq(&self, other: &Self) -> bool {
375 let self_in_build = !self.build.is_empty() && self.offsets.is_empty();
376 let other_in_build = !other.build.is_empty() && other.offsets.is_empty();
377 debug_assert!(
378 self_in_build == other_in_build,
379 "FlatVecVec::eq called across phases (one build, one flattened)"
380 );
381 if self_in_build || other_in_build {
382 self.build == other.build
383 } else {
384 self.data == other.data && self.offsets == other.offsets
385 }
386 }
387}
388
389impl<T> Default for FlatVecVec<T> {
390 fn default() -> Self {
391 Self {
392 build: Vec::new(),
393 data: Vec::new(),
394 offsets: Vec::new(),
395 }
396 }
397}
398
399impl<T> FlatVecVec<T> {
400 #[must_use] pub fn heap_bytes(&self, per_element_size: usize) -> usize {
405 let data_bytes = self.data.capacity() * per_element_size;
406 let offsets_bytes =
407 self.offsets.capacity() * size_of::<(u32, u32)>();
408 let mut build_bytes = self.build.capacity() * size_of::<Vec<T>>();
409 for v in &self.build {
410 build_bytes += v.capacity() * per_element_size;
411 }
412 data_bytes + offsets_bytes + build_bytes
413 }
414
415 #[must_use] pub fn new(node_count: usize) -> Self {
417 let mut build = Vec::with_capacity(node_count);
418 for _ in 0..node_count {
419 build.push(Vec::new());
420 }
421 Self {
422 build,
423 data: Vec::new(),
424 offsets: Vec::new(),
425 }
426 }
427
428 #[inline]
433 pub fn push_to(&mut self, node_index: usize, item: T) {
434 self.build[node_index].push(item);
435 }
436
437 #[inline]
439 pub fn build_mut(&mut self, node_index: usize) -> &mut Vec<T> {
440 &mut self.build[node_index]
441 }
442
443 #[inline]
445 pub fn build_iter_mut(&mut self) -> core::slice::IterMut<'_, Vec<T>> {
446 self.build.iter_mut()
447 }
448
449 #[inline]
452 #[must_use] pub fn build_get(&self, node_index: usize) -> Option<&Vec<T>> {
453 self.build.get(node_index)
454 }
455
456 #[inline]
458 #[must_use] pub const fn len(&self) -> usize {
459 if self.offsets.is_empty() {
460 self.build.len()
461 } else {
462 self.offsets.len()
463 }
464 }
465
466 #[inline]
468 #[must_use] pub const fn is_empty(&self) -> bool {
469 self.len() == 0
470 }
471
472 #[inline]
474 #[must_use] pub const fn is_flattened(&self) -> bool {
475 !self.offsets.is_empty() || self.build.is_empty()
476 }
477
478 #[inline]
482 #[must_use] pub fn get_slice(&self, node_index: usize) -> &[T] {
483 if self.offsets.is_empty() {
484 self.build.get(node_index).map_or(&[], alloc::vec::Vec::as_slice)
486 } else {
487 if let Some(&(start, len)) = self.offsets.get(node_index) {
489 let s = start as usize;
490 let l = len as usize;
491 &self.data[s..s + l]
492 } else {
493 &[]
494 }
495 }
496 }
497
498 pub fn sort_each_and_flatten<K: Ord + Eq>(&mut self, key_fn: impl Fn(&T) -> K) {
503 let node_count = self.build.len();
504 let total: usize = self.build.iter().map(alloc::vec::Vec::len).sum();
505
506 let mut flat_data = Vec::with_capacity(total);
507 let mut offsets = Vec::with_capacity(node_count);
508
509 for inner in &mut self.build {
510 inner.sort_by_key(|a| key_fn(a));
511
512 let n = inner.len();
514 let mut keep = vec![false; n];
515 for i in 0..n {
516 if i + 1 >= n || key_fn(&inner[i]) != key_fn(&inner[i + 1]) {
517 keep[i] = true;
518 }
519 }
520
521 let start = u32::try_from(flat_data.len()).unwrap_or(u32::MAX);
522 for (i, item) in inner.drain(..).enumerate() {
524 if keep[i] {
525 flat_data.push(item);
526 }
527 }
528
529 let len = u32::try_from(flat_data.len()).unwrap_or(u32::MAX) - start;
530 offsets.push((start, len));
531 }
532
533 flat_data.shrink_to_fit();
534 self.data = flat_data;
535 self.offsets = offsets;
536 self.build = Vec::new();
537 }
538
539 pub fn flatten(&mut self) {
541 let node_count = self.build.len();
542 let total: usize = self.build.iter().map(alloc::vec::Vec::len).sum();
543
544 let mut flat_data = Vec::with_capacity(total);
545 let mut offsets = Vec::with_capacity(node_count);
546
547 for inner in &mut self.build {
548 let start = u32::try_from(flat_data.len()).unwrap_or(u32::MAX);
549 let len = u32::try_from(inner.len()).unwrap_or(u32::MAX);
550 offsets.push((start, len));
551 flat_data.append(inner);
552 }
553
554 self.data = flat_data;
555 self.offsets = offsets;
556 self.build = Vec::new();
557 }
558
559 pub fn retain(&mut self, predicate: impl Fn(&T) -> bool) where T: Clone {
562 if self.offsets.is_empty() { return; }
563 let node_count = self.offsets.len();
564 let mut new_data = Vec::new();
565 let mut new_offsets = Vec::with_capacity(node_count);
566 for &(start, len) in &self.offsets {
567 let s = start as usize;
568 let l = len as usize;
569 let new_start = u32::try_from(new_data.len()).unwrap_or(u32::MAX);
570 let slice = &self.data[s..s + l];
571 let mut kept = 0u32;
572 for item in slice {
573 if predicate(item) {
574 new_data.push((*item).clone());
575 kept += 1;
576 }
577 }
578 new_offsets.push((new_start, kept));
579 }
580 new_data.shrink_to_fit();
581 self.data = new_data;
582 self.offsets = new_offsets;
583 }
584
585 pub fn retain_with_node_index(
588 &mut self,
589 predicate: impl Fn(usize, &T) -> bool,
590 ) where T: Clone {
591 if self.offsets.is_empty() { return; }
592 let node_count = self.offsets.len();
593 let mut new_data = Vec::new();
594 let mut new_offsets = Vec::with_capacity(node_count);
595 for (node_idx, &(start, len)) in self.offsets.iter().enumerate() {
596 let s = start as usize;
597 let l = len as usize;
598 let new_start = u32::try_from(new_data.len()).unwrap_or(u32::MAX);
599 let slice = &self.data[s..s + l];
600 let mut kept = 0u32;
601 for item in slice {
602 if predicate(node_idx, item) {
603 new_data.push((*item).clone());
604 kept += 1;
605 }
606 }
607 new_offsets.push((new_start, kept));
608 }
609 new_data.shrink_to_fit();
610 self.data = new_data;
611 self.offsets = new_offsets;
612 }
613
614 pub(crate) const fn iter_node_slices(&self) -> FlatVecVecIter<'_, T> {
617 FlatVecVecIter {
618 fvv: self,
619 idx: 0,
620 count: self.len(),
621 }
622 }
623
624 pub fn extend_from(&mut self, other: &mut Self) {
627 if !self.offsets.is_empty() && !other.offsets.is_empty() {
628 let base = u32::try_from(self.data.len()).unwrap_or(u32::MAX);
630 self.data.append(&mut other.data);
631 self.offsets.extend(other.offsets.drain(..).map(|(s, l)| (s + base, l)));
632 } else {
633 self.build.append(&mut other.build);
635 self.data.clear();
637 self.offsets.clear();
638 }
639 }
640}
641
642pub(crate) struct FlatVecVecIter<'a, T> {
644 fvv: &'a FlatVecVec<T>,
645 idx: usize,
646 count: usize,
647}
648
649impl<'a, T> Iterator for FlatVecVecIter<'a, T> {
650 type Item = (usize, &'a [T]);
651
652 #[inline]
653 fn next(&mut self) -> Option<Self::Item> {
654 if self.idx >= self.count {
655 return None;
656 }
657 let i = self.idx;
658 self.idx += 1;
659 Some((i, self.fvv.get_slice(i)))
660 }
661
662 fn size_hint(&self) -> (usize, Option<usize>) {
663 let rem = self.count - self.idx;
664 (rem, Some(rem))
665 }
666}
667
668impl<T> ExactSizeIterator for FlatVecVecIter<'_, T> {}
669
670#[derive(Debug, Default, Clone, PartialEq)]
682pub struct CssPropertyCache {
683 pub node_count: usize,
685
686 pub user_overridden_properties: Vec<Vec<(CssPropertyType, CssProperty)>>,
688
689 pub cascaded_props: FlatVecVec<StatefulCssProperty>,
693
694 pub css_props: FlatVecVec<StatefulCssProperty>,
697
698 pub computed_values: Vec<Vec<(CssPropertyType, CssPropertyWithOrigin)>>,
700
701 pub compact_cache: Option<azul_css::compact_cache::CompactLayoutCache>,
705
706 pub global_css_props: Vec<CssProperty>,
710
711 pub resolved_font_sizes_px: crate::sync::OnceLock<Vec<f32>>,
725}
726
727#[derive(Debug, Clone, Copy, Default)]
735pub struct CssPropertyCacheBreakdown {
736 pub node_count: usize,
737 pub cascaded_props_bytes: usize,
738 pub css_props_bytes: usize,
739 pub computed_values_bytes: usize,
740 pub user_overridden_bytes: usize,
741 pub global_css_props_bytes: usize,
742 pub compact_cache_bytes: usize,
743 pub resolved_font_sizes_bytes: usize,
744}
745
746impl CssPropertyCacheBreakdown {
747 #[must_use] pub const fn total_bytes(&self) -> usize {
749 self.cascaded_props_bytes
750 + self.css_props_bytes
751 + self.computed_values_bytes
752 + self.user_overridden_bytes
753 + self.global_css_props_bytes
754 + self.compact_cache_bytes
755 + self.resolved_font_sizes_bytes
756 }
757}
758
759impl CssPropertyCache {
760 pub fn memory_breakdown(&self) -> CssPropertyCacheBreakdown {
774 let stateful_sz = size_of::<StatefulCssProperty>();
775 let computed_entry_sz =
776 size_of::<(CssPropertyType, CssPropertyWithOrigin)>();
777 let outer_vec_sz = size_of::<Vec<(CssPropertyType, CssPropertyWithOrigin)>>();
778
779 let cascaded_bytes = self.cascaded_props.heap_bytes(stateful_sz);
780 let css_bytes = self.css_props.heap_bytes(stateful_sz);
781
782 let mut computed_bytes = self.computed_values.capacity() * outer_vec_sz;
783 for v in &self.computed_values {
784 computed_bytes += v.capacity() * computed_entry_sz;
785 }
786
787 let user_overridden_bytes = {
788 let mut b = self.user_overridden_properties.capacity() * outer_vec_sz;
789 for v in &self.user_overridden_properties {
790 b += v.capacity()
791 * size_of::<(CssPropertyType, CssProperty)>();
792 }
793 b
794 };
795
796 let global_bytes = self.global_css_props.capacity()
797 * size_of::<CssProperty>();
798
799 let compact_bytes = self
800 .compact_cache
801 .as_ref()
802 .map_or(0, |c| {
803 c.tier1_enums.capacity() * 8
804 + c.tier2_dims.capacity() * 68
805 + c.tier2_cold.capacity() * 28
806 + c.tier2b_text.capacity() * 24
807 + c.prev_font_hashes.capacity() * 8
808 + c.font_dirty_nodes.capacity() * 8
809 });
810
811 let resolved_font_sizes_bytes = self
812 .resolved_font_sizes_px
813 .get()
814 .map_or(0, |v| v.capacity() * size_of::<f32>());
815
816 CssPropertyCacheBreakdown {
817 node_count: self.node_count,
818 cascaded_props_bytes: cascaded_bytes,
819 css_props_bytes: css_bytes,
820 computed_values_bytes: computed_bytes,
821 user_overridden_bytes,
822 global_css_props_bytes: global_bytes,
823 compact_cache_bytes: compact_bytes,
824 resolved_font_sizes_bytes,
825 }
826 }
827
828 pub fn prune_compact_normal_props(&mut self) {
834 use azul_css::dynamic_selector::PseudoStateType;
835
836 #[cfg(feature = "std")]
837 {
838 static PRUNE_DBG: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
839 let dbg = *PRUNE_DBG.get_or_init(crate::profile::memory_enabled);
840 if dbg {
841 let mut normal_compact = 0usize;
842 let mut normal_noncompact = 0usize;
843 let mut nonnormal = 0usize;
844 for i in 0..self.css_props.len() {
845 for p in self.css_props.get_slice(i) {
846 if p.state != PseudoStateType::Normal {
847 nonnormal += 1;
848 } else if p.prop_type.has_compact_encoding() {
849 normal_compact += 1;
850 } else {
851 normal_noncompact += 1;
852 }
853 }
854 }
855 let ssp_sz = size_of::<StatefulCssProperty>();
856 let mut casc_normal_compact = 0usize;
857 let mut casc_total = 0usize;
858 for i in 0..self.cascaded_props.len() {
859 for p in self.cascaded_props.get_slice(i) {
860 casc_total += 1;
861 if p.state == PseudoStateType::Normal && p.prop_type.has_compact_encoding() {
862 casc_normal_compact += 1;
863 }
864 }
865 }
866 eprintln!("[PRUNE] css_props: norm+compact={normal_compact} norm+other={normal_noncompact} nonnorm={nonnormal} SSP={ssp_sz}B | cascaded: total={casc_total} norm+compact={casc_normal_compact}");
867 }
868 }
869
870 let keep = |p: &StatefulCssProperty| -> bool {
876 if p.state != PseudoStateType::Normal {
877 return true;
878 }
879 if !p.prop_type.has_compact_encoding() {
880 return true;
881 }
882 if property_needs_slow_path_after_compact(&p.property) {
885 return true;
886 }
887 false
888 };
889 if !self.cascaded_props.is_flattened() {
903 self.cascaded_props.sort_each_and_flatten(|p| (p.state, p.prop_type));
904 }
905 self.cascaded_props.retain(keep);
906 }
907
908 #[inline]
911 #[allow(clippy::trivially_copy_pass_by_ref)]
916 fn find_in_stateful<'a>(
917 props: &'a [StatefulCssProperty],
918 state: azul_css::dynamic_selector::PseudoStateType,
919 prop_type: &CssPropertyType,
920 ) -> Option<&'a CssProperty> {
921 let key = (state, *prop_type);
922 props.binary_search_by_key(&key, |p| (p.state, p.prop_type))
923 .ok()
924 .map(|idx| &props[idx].property)
925 }
926
927 #[inline]
930 fn has_state_props(
931 props: &[StatefulCssProperty],
932 state: azul_css::dynamic_selector::PseudoStateType,
933 ) -> bool {
934 let i = props.partition_point(|p| p.state < state);
937 i < props.len() && props[i].state == state
938 }
939
940 pub(crate) fn prop_types_for_state(
942 props: &[StatefulCssProperty],
943 state: azul_css::dynamic_selector::PseudoStateType,
944 ) -> impl Iterator<Item = &CssPropertyType> + '_ {
945 props.iter().filter(move |p| p.state == state).map(|p| &p.prop_type)
946 }
947}
948
949fn property_needs_slow_path_after_compact(prop: &CssProperty) -> bool {
959 use azul_css::css::CssPropertyValue;
960 use azul_css::props::{
961 basic::length::SizeMetric,
962 layout::{
963 dimensions::{LayoutHeight, LayoutWidth},
964 flex::LayoutFlexBasis,
965 },
966 };
967
968 macro_rules! check_plain {
970 ($v:expr) => {{
971 if let CssPropertyValue::Exact(ref inner) = $v {
972 return inner.inner.metric != SizeMetric::Px;
973 }
974 false
975 }};
976 }
977
978 match prop {
979 CssProperty::Width(v) => {
983 if let CssPropertyValue::Exact(LayoutWidth::Px(pv)) = v {
984 return pv.metric != SizeMetric::Px;
985 }
986 false
987 }
988 CssProperty::Height(v) => {
989 if let CssPropertyValue::Exact(LayoutHeight::Px(pv)) = v {
990 return pv.metric != SizeMetric::Px;
991 }
992 false
993 }
994
995 CssProperty::FlexBasis(v) => {
997 if let CssPropertyValue::Exact(LayoutFlexBasis::Exact(pv)) = v {
998 return pv.metric != SizeMetric::Px;
999 }
1000 false
1001 }
1002
1003 CssProperty::MinWidth(v) => check_plain!(v),
1005 CssProperty::MaxWidth(v) => check_plain!(v),
1006 CssProperty::MinHeight(v) => check_plain!(v),
1007 CssProperty::MaxHeight(v) => check_plain!(v),
1008 CssProperty::FontSize(v) => check_plain!(v),
1009 CssProperty::PaddingTop(v) => check_plain!(v),
1010 CssProperty::PaddingRight(v) => check_plain!(v),
1011 CssProperty::PaddingBottom(v) => check_plain!(v),
1012 CssProperty::PaddingLeft(v) => check_plain!(v),
1013 CssProperty::MarginTop(v) => check_plain!(v),
1014 CssProperty::MarginRight(v) => check_plain!(v),
1015 CssProperty::MarginBottom(v) => check_plain!(v),
1016 CssProperty::MarginLeft(v) => check_plain!(v),
1017 CssProperty::BorderTopWidth(v) => check_plain!(v),
1018 CssProperty::BorderRightWidth(v) => check_plain!(v),
1019 CssProperty::BorderBottomWidth(v) => check_plain!(v),
1020 CssProperty::BorderLeftWidth(v) => check_plain!(v),
1021 CssProperty::Top(v) => check_plain!(v),
1022 CssProperty::Right(v) => check_plain!(v),
1023 CssProperty::Bottom(v) => check_plain!(v),
1024 CssProperty::Left(v) => check_plain!(v),
1025 CssProperty::ColumnGap(v) => check_plain!(v),
1026 CssProperty::RowGap(v) => check_plain!(v),
1027 CssProperty::LetterSpacing(v) => check_plain!(v),
1028 CssProperty::WordSpacing(v) => check_plain!(v),
1029 CssProperty::TextIndent(v) => check_plain!(v),
1030 CssProperty::TabSize(v) => check_plain!(v),
1031
1032 _ => false,
1034 }
1035}
1036
1037fn clone_inheritable_property(
1050 p: &CssProperty,
1051) -> CssProperty {
1052 use azul_css::props::property::CssProperty;
1053 if let CssProperty::FontFamily(v) = p { return CssProperty::FontFamily(v.clone()); }
1054 if let CssProperty::BackgroundContent(v) = p { return CssProperty::BackgroundContent(v.clone()); }
1055 if let CssProperty::BackgroundPosition(v) = p { return CssProperty::BackgroundPosition(v.clone()); }
1056 if let CssProperty::BackgroundSize(v) = p { return CssProperty::BackgroundSize(v.clone()); }
1057 if let CssProperty::BackgroundRepeat(v) = p { return CssProperty::BackgroundRepeat(v.clone()); }
1058 if let CssProperty::BoxShadowLeft(v) = p { return CssProperty::BoxShadowLeft(v.clone()); }
1059 if let CssProperty::BoxShadowRight(v) = p { return CssProperty::BoxShadowRight(v.clone()); }
1060 if let CssProperty::BoxShadowTop(v) = p { return CssProperty::BoxShadowTop(v.clone()); }
1061 if let CssProperty::BoxShadowBottom(v) = p { return CssProperty::BoxShadowBottom(v.clone()); }
1062 if let CssProperty::TextShadow(v) = p { return CssProperty::TextShadow(v.clone()); }
1063 if let CssProperty::ScrollbarTrack(v) = p { return CssProperty::ScrollbarTrack(v.clone()); }
1064 if let CssProperty::ScrollbarThumb(v) = p { return CssProperty::ScrollbarThumb(v.clone()); }
1065 if let CssProperty::ScrollbarButton(v) = p { return CssProperty::ScrollbarButton(v.clone()); }
1066 if let CssProperty::ScrollbarCorner(v) = p { return CssProperty::ScrollbarCorner(v.clone()); }
1067 if let CssProperty::ScrollbarResizer(v) = p { return CssProperty::ScrollbarResizer(v.clone()); }
1068 if let CssProperty::Transform(v) = p { return CssProperty::Transform(v.clone()); }
1069 if let CssProperty::Filter(v) = p { return CssProperty::Filter(v.clone()); }
1070 if let CssProperty::BackdropFilter(v) = p { return CssProperty::BackdropFilter(v.clone()); }
1071 if let CssProperty::Content(v) = p { return CssProperty::Content(v.clone()); }
1072 if let CssProperty::HyphenationLanguage(v) = p { return CssProperty::HyphenationLanguage(v.clone()); }
1073 if let CssProperty::Cursor(v) = p { return CssProperty::Cursor(*v); }
1074 p.clone()
1075}
1076
1077impl CssPropertyCache {
1078 #[must_use]
1082 #[allow(clippy::too_many_lines, clippy::cognitive_complexity)] pub fn restyle(
1084 &mut self,
1085 css: &mut Css,
1086 node_data: &NodeDataContainerRef<'_, NodeData>,
1087 node_hierarchy: &NodeHierarchyItemVec,
1088 non_leaf_nodes: &ParentWithNodeDepthVec,
1089 html_tree: &NodeDataContainerRef<'_, CascadeInfo>,
1090 ) -> Vec<TagIdToNodeIdMapping> {
1091 use azul_css::{
1092 css::{CssDeclaration, CssPathPseudoSelector::{Hover, Active, Focus, Dragging, DragOver}, CssPathSelector, CssRuleBlock},
1093 dynamic_selector::{DynamicSelector, PseudoStateType},
1094 props::layout::LayoutDisplay,
1095 };
1096
1097 let css_is_empty = css.is_empty();
1098
1099 if !css_is_empty {
1100 css.sort_by_specificity();
1101
1102 let mut global_only_rules: Vec<&CssRuleBlock> = Vec::new();
1107 let mut specific_rules: Vec<&CssRuleBlock> = Vec::new();
1108
1109 for rule in css.rules() {
1110 let selectors = rule.path.selectors.as_ref();
1111 let is_global_only = selectors.len() == 1
1112 && matches!(selectors.first(), Some(CssPathSelector::Global));
1113 if is_global_only {
1114 global_only_rules.push(rule);
1115 } else {
1116 specific_rules.push(rule);
1117 }
1118 }
1119
1120 for entry in self.css_props.build_iter_mut() { entry.clear(); }
1122
1123 self.global_css_props.clear();
1128 for rule in &global_only_rules {
1129 if crate::style::rule_ends_with(&rule.path, None) {
1130 for d in &rule.declarations {
1131 if let CssDeclaration::Static(s) = d {
1132 self.global_css_props.push(s.clone());
1133 }
1134 }
1135 }
1136 }
1137
1138 if !specific_rules.is_empty() {
1140
1141 macro_rules! filter_rules {($expected_pseudo_selector:expr, $node_id:expr) => {{
1151 let mut out: Vec<(u16, u16)> = Vec::new();
1152 for (rule_idx, rule_block) in specific_rules.iter().enumerate() {
1153 if !crate::style::rule_ends_with(&rule_block.path, $expected_pseudo_selector) {
1154 continue;
1155 }
1156 if !crate::style::matches_html_element(
1157 &rule_block.path,
1158 $node_id,
1159 &node_hierarchy.as_container(),
1160 &node_data,
1161 &html_tree,
1162 $expected_pseudo_selector,
1163 ) {
1164 continue;
1165 }
1166 for (decl_idx, decl) in rule_block.declarations.as_slice().iter().enumerate() {
1167 if matches!(decl, CssDeclaration::Static(_)) {
1168 out.push((u16::try_from(rule_idx).unwrap_or(u16::MAX), u16::try_from(decl_idx).unwrap_or(u16::MAX)));
1169 }
1170 }
1171 }
1172 out
1173 }};}
1174
1175 let has_normal = specific_rules.iter().any(|r| crate::style::rule_ends_with(&r.path, None));
1179 let has_hover = specific_rules.iter().any(|r| crate::style::rule_ends_with(&r.path, Some(Hover)));
1180 let has_active = specific_rules.iter().any(|r| crate::style::rule_ends_with(&r.path, Some(Active)));
1181 let has_focus = specific_rules.iter().any(|r| crate::style::rule_ends_with(&r.path, Some(Focus)));
1182 let has_dragging = specific_rules.iter().any(|r| crate::style::rule_ends_with(&r.path, Some(Dragging)));
1183 let has_drag_over = specific_rules.iter().any(|r| crate::style::rule_ends_with(&r.path, Some(DragOver)));
1184
1185 macro_rules! collect_and_assign {
1186 ($pseudo:expr, $state:expr, $has_any:expr) => {
1187 if $has_any {
1188 let indices: NodeDataContainer<(NodeId, Vec<(u16, u16)>)> = node_data
1189 .transform_nodeid_optional(|node_id| {
1190 let r = filter_rules!($pseudo, node_id);
1191 if r.is_empty() { None } else { Some((node_id, r)) }
1192 });
1193 for (n, pairs) in indices.internal.into_iter() {
1194 for (rule_idx, decl_idx) in pairs {
1195 let decl = &specific_rules[rule_idx as usize]
1196 .declarations
1197 .as_slice()[decl_idx as usize];
1198 if let CssDeclaration::Static(prop) = decl {
1199 self.css_props.push_to(n.index(), StatefulCssProperty {
1200 state: $state,
1201 prop_type: prop.get_type(),
1202 property: prop.clone(),
1203 });
1204 }
1205 }
1206 }
1207 }
1208 };
1209 }
1210
1211 collect_and_assign!(None, PseudoStateType::Normal, has_normal);
1212 collect_and_assign!(Some(Hover), PseudoStateType::Hover, has_hover);
1213 collect_and_assign!(Some(Active), PseudoStateType::Active, has_active);
1214 collect_and_assign!(Some(Focus), PseudoStateType::Focus, has_focus);
1215 collect_and_assign!(Some(Dragging), PseudoStateType::Dragging, has_dragging);
1216 collect_and_assign!(Some(DragOver), PseudoStateType::DragOver, has_drag_over);
1217
1218 } }
1220
1221 for ParentWithNodeDepth { depth: _, node_id } in non_leaf_nodes {
1224 let Some(parent_id) = node_id.into_crate_internal() else {
1225 continue;
1226 };
1227
1228 let all_states = [
1229 PseudoStateType::Normal,
1230 PseudoStateType::Hover,
1231 PseudoStateType::Active,
1232 PseudoStateType::Focus,
1233 PseudoStateType::Dragging,
1234 PseudoStateType::DragOver,
1235 ];
1236
1237 for &state in &all_states {
1238 let parent_inheritable_inline: Vec<(CssPropertyType, CssProperty)> = node_data[parent_id]
1240 .style
1241 .iter_inline_properties()
1242 .filter(|(_prop, conds)| {
1243 let conditions = conds.as_slice();
1244 if conditions.is_empty() {
1245 state == PseudoStateType::Normal
1246 } else {
1247 conditions.iter().all(|c| {
1248 matches!(c, DynamicSelector::PseudoState(s) if *s == state)
1249 })
1250 }
1251 })
1252 .map(|(prop, _)| prop)
1253 .filter(|prop| prop.get_type().is_inheritable())
1254 .map(|p| (p.get_type(), clone_inheritable_property(p)))
1255 .collect();
1256
1257 let parent_inheritable_css: Vec<(CssPropertyType, CssProperty)> = if css_is_empty {
1259 Vec::new()
1260 } else {
1261 self.css_props.get_slice(parent_id.index())
1262 .iter()
1263 .filter(|p| p.state == state && p.prop_type.is_inheritable())
1264 .map(|p| (p.prop_type, clone_inheritable_property(&p.property)))
1265 .collect()
1266 };
1267
1268 let parent_inheritable_cascaded: Vec<(CssPropertyType, CssProperty)> =
1270 self.cascaded_props.get_slice(parent_id.index())
1271 .iter()
1272 .filter(|p| p.state == state && p.prop_type.is_inheritable())
1273 .map(|p| (p.prop_type, clone_inheritable_property(&p.property)))
1274 .collect();
1275
1276 if parent_inheritable_inline.is_empty()
1279 && parent_inheritable_css.is_empty()
1280 && parent_inheritable_cascaded.is_empty()
1281 {
1282 continue;
1283 }
1284
1285 for child_id in parent_id.az_children(&node_hierarchy.as_container()) {
1286 let child_vec = self.cascaded_props.build_mut(child_id.index());
1287 for (prop_type, prop_value) in parent_inheritable_inline
1288 .iter()
1289 .chain(parent_inheritable_css.iter())
1290 .chain(parent_inheritable_cascaded.iter())
1291 {
1292 if !child_vec.iter().any(|p| p.state == state && p.prop_type == *prop_type) {
1294 child_vec.push(StatefulCssProperty {
1295 state,
1296 prop_type: *prop_type,
1297 property: prop_value.clone(),
1298 });
1299 }
1300 }
1301 }
1302 }
1303 }
1304
1305 self.css_props.sort_each_and_flatten(|p| (p.state, p.prop_type));
1308
1309 self.invalidate_resolved_font_sizes();
1312
1313 self.generate_tag_ids(node_data, node_hierarchy)
1314 }
1315
1316 pub fn generate_tag_ids(
1320 &self,
1321 node_data: &NodeDataContainerRef<'_, NodeData>,
1322 node_hierarchy: &NodeHierarchyItemVec,
1323 ) -> Vec<TagIdToNodeIdMapping> {
1324
1325 use azul_css::compact_cache::{
1329 DISPLAY_SHIFT, DISPLAY_MASK,
1330 OVERFLOW_X_SHIFT, OVERFLOW_Y_SHIFT, OVERFLOW_MASK,
1331 };
1332
1333 let compact_cache = self.compact_cache.as_ref();
1334 let node_data_container = &node_data.internal;
1335
1336 let tag_ids = node_data
1337 .internal
1338 .iter()
1339 .enumerate()
1340 .filter_map(|(node_idx, node_data)| {
1341 let node_id = NodeId::new(node_idx);
1342
1343 let should_auto_insert_tabindex = node_data
1344 .get_callbacks()
1345 .iter()
1346 .any(|cb| cb.event.is_focus_callback());
1347
1348 let tab_index = node_data.get_tab_index().map_or(if should_auto_insert_tabindex {
1349 Some(TabIndex::Auto)
1350 } else {
1351 None
1352 }, Some);
1353
1354 let mut need_tag = false;
1355
1356 'compute_need_tag: {
1360 if let Some(cc) = compact_cache.as_ref() {
1362 let t1 = cc.tier1_enums[node_idx];
1363 let display_val = ((t1 >> DISPLAY_SHIFT) & DISPLAY_MASK) as u8;
1364 if display_val == 4 { break 'compute_need_tag; } }
1366
1367 if node_data.has_context_menu() || node_data.get_context_menu().is_some() {
1368 need_tag = true; break 'compute_need_tag;
1369 }
1370 if tab_index.is_some() { need_tag = true; break 'compute_need_tag; }
1371
1372 {
1374 use azul_css::dynamic_selector::{DynamicSelector, PseudoStateType};
1375 let has_pseudo = |state: PseudoStateType| -> bool {
1376 node_data.style.iter_inline_properties().any(|(_p, conds)| {
1377 conds.as_slice().iter().any(|c|
1378 matches!(c, DynamicSelector::PseudoState(s) if *s == state)
1379 )
1380 }) || Self::has_state_props(self.css_props.get_slice(node_idx), state)
1381 };
1382
1383 if has_pseudo(PseudoStateType::Hover)
1384 || has_pseudo(PseudoStateType::Active)
1385 || has_pseudo(PseudoStateType::Focus)
1386 || has_pseudo(PseudoStateType::Dragging)
1387 || has_pseudo(PseudoStateType::DragOver)
1388 {
1389 need_tag = true; break 'compute_need_tag;
1390 }
1391 }
1392
1393 let has_non_window_cb = !node_data.get_callbacks().is_empty()
1395 && !node_data.get_callbacks().iter().all(|cb| cb.event.is_window_callback());
1396 if has_non_window_cb { need_tag = true; break 'compute_need_tag; }
1397
1398 if self.css_props.get_slice(node_idx).iter().any(|p|
1400 p.state == azul_css::dynamic_selector::PseudoStateType::Normal
1401 && p.prop_type == CssPropertyType::Cursor
1402 ) || node_data.style.iter_inline_properties().any(|(p, _)|
1403 p.get_type() == CssPropertyType::Cursor
1404 ) {
1405 need_tag = true; break 'compute_need_tag;
1406 }
1407
1408 if let Some(cc) = compact_cache.as_ref() {
1410 let t1 = cc.tier1_enums[node_idx];
1411 let ox = ((t1 >> OVERFLOW_X_SHIFT) & OVERFLOW_MASK) as u8;
1412 let oy = ((t1 >> OVERFLOW_Y_SHIFT) & OVERFLOW_MASK) as u8;
1413 if ox == 2 || ox == 3 || oy == 2 || oy == 3 {
1415 need_tag = true; break 'compute_need_tag;
1416 }
1417 }
1418
1419 {
1421 use crate::dom::NodeType;
1422 let hier = node_hierarchy.as_container()[node_id];
1423 let mut has_text = false;
1424 if let Some(first_child) = hier.first_child_id(node_id) {
1425 let mut child_id = Some(first_child);
1426 while let Some(cid) = child_id {
1427 if matches!(node_data_container[cid.index()].get_node_type(), NodeType::Text(_)) {
1428 has_text = true; break;
1429 }
1430 child_id = node_hierarchy.as_container()[cid].next_sibling_id();
1431 }
1432 }
1433 if has_text { need_tag = true; break 'compute_need_tag; }
1434 }
1435
1436 break 'compute_need_tag;
1437 }
1438
1439 if need_tag {
1440 Some(TagIdToNodeIdMapping {
1441 tag_id: TagId::from_crate_internal(TagId::unique()),
1442 node_id: NodeHierarchyItemId::from_crate_internal(Some(node_id)),
1443 tab_index: tab_index.into(),
1444 })
1445 } else {
1446 None
1447 }
1448 })
1449 .collect::<Vec<_>>();
1450
1451 tag_ids
1452 }
1453
1454 #[allow(clippy::too_many_lines, clippy::cognitive_complexity)] pub fn get_computed_css_style_string(
1456 &self,
1457 node_data: &NodeData,
1458 node_id: &NodeId,
1459 node_state: &StyledNodeState,
1460 ) -> String {
1461 let mut s = String::new();
1462 if let Some(p) = self.get_background_content(node_data, node_id, node_state) {
1463 let _ = write!(s,"background: {};", p.get_css_value_fmt());
1464 }
1465 if let Some(p) = self.get_background_position(node_data, node_id, node_state) {
1466 let _ = write!(s,"background-position: {};", p.get_css_value_fmt());
1467 }
1468 if let Some(p) = self.get_background_size(node_data, node_id, node_state) {
1469 let _ = write!(s,"background-size: {};", p.get_css_value_fmt());
1470 }
1471 if let Some(p) = self.get_background_repeat(node_data, node_id, node_state) {
1472 let _ = write!(s,"background-repeat: {};", p.get_css_value_fmt());
1473 }
1474 if let Some(p) = self.get_font_size(node_data, node_id, node_state) {
1475 let _ = write!(s,"font-size: {};", p.get_css_value_fmt());
1476 }
1477 if let Some(p) = self.get_font_family(node_data, node_id, node_state) {
1478 let _ = write!(s,"font-family: {};", p.get_css_value_fmt());
1479 }
1480 if let Some(p) = self.get_text_color(node_data, node_id, node_state) {
1481 let _ = write!(s,"color: {};", p.get_css_value_fmt());
1482 }
1483 if let Some(p) = self.get_text_align(node_data, node_id, node_state) {
1484 let _ = write!(s,"text-align: {};", p.get_css_value_fmt());
1485 }
1486 if let Some(p) = self.get_line_height(node_data, node_id, node_state) {
1487 let _ = write!(s,"line-height: {};", p.get_css_value_fmt());
1488 }
1489 if let Some(p) = self.get_letter_spacing(node_data, node_id, node_state) {
1490 let _ = write!(s,"letter-spacing: {};", p.get_css_value_fmt());
1491 }
1492 if let Some(p) = self.get_word_spacing(node_data, node_id, node_state) {
1493 let _ = write!(s,"word-spacing: {};", p.get_css_value_fmt());
1494 }
1495 if let Some(p) = self.get_tab_size(node_data, node_id, node_state) {
1496 let _ = write!(s,"tab-size: {};", p.get_css_value_fmt());
1497 }
1498 if let Some(p) = self.get_cursor(node_data, node_id, node_state) {
1499 let _ = write!(s,"cursor: {};", p.get_css_value_fmt());
1500 }
1501 if let Some(p) = self.get_box_shadow_left(node_data, node_id, node_state) {
1502 let _ = write!(s,
1503 "-azul-box-shadow-left: {};",
1504 p.get_css_value_fmt()
1505 );
1506 }
1507 if let Some(p) = self.get_box_shadow_right(node_data, node_id, node_state) {
1508 let _ = write!(s,
1509 "-azul-box-shadow-right: {};",
1510 p.get_css_value_fmt()
1511 );
1512 }
1513 if let Some(p) = self.get_box_shadow_top(node_data, node_id, node_state) {
1514 let _ = write!(s,"-azul-box-shadow-top: {};", p.get_css_value_fmt());
1515 }
1516 if let Some(p) = self.get_box_shadow_bottom(node_data, node_id, node_state) {
1517 let _ = write!(s,
1518 "-azul-box-shadow-bottom: {};",
1519 p.get_css_value_fmt()
1520 );
1521 }
1522 if let Some(p) = self.get_border_top_color(node_data, node_id, node_state) {
1523 let _ = write!(s,"border-top-color: {};", p.get_css_value_fmt());
1524 }
1525 if let Some(p) = self.get_border_left_color(node_data, node_id, node_state) {
1526 let _ = write!(s,"border-left-color: {};", p.get_css_value_fmt());
1527 }
1528 if let Some(p) = self.get_border_right_color(node_data, node_id, node_state) {
1529 let _ = write!(s,"border-right-color: {};", p.get_css_value_fmt());
1530 }
1531 if let Some(p) = self.get_border_bottom_color(node_data, node_id, node_state) {
1532 let _ = write!(s,"border-bottom-color: {};", p.get_css_value_fmt());
1533 }
1534 if let Some(p) = self.get_border_top_style(node_data, node_id, node_state) {
1535 let _ = write!(s,"border-top-style: {};", p.get_css_value_fmt());
1536 }
1537 if let Some(p) = self.get_border_left_style(node_data, node_id, node_state) {
1538 let _ = write!(s,"border-left-style: {};", p.get_css_value_fmt());
1539 }
1540 if let Some(p) = self.get_border_right_style(node_data, node_id, node_state) {
1541 let _ = write!(s,"border-right-style: {};", p.get_css_value_fmt());
1542 }
1543 if let Some(p) = self.get_border_bottom_style(node_data, node_id, node_state) {
1544 let _ = write!(s,"border-bottom-style: {};", p.get_css_value_fmt());
1545 }
1546 if let Some(p) = self.get_border_top_left_radius(node_data, node_id, node_state) {
1547 let _ = write!(s,
1548 "border-top-left-radius: {};",
1549 p.get_css_value_fmt()
1550 );
1551 }
1552 if let Some(p) = self.get_border_top_right_radius(node_data, node_id, node_state) {
1553 let _ = write!(s,
1554 "border-top-right-radius: {};",
1555 p.get_css_value_fmt()
1556 );
1557 }
1558 if let Some(p) = self.get_border_bottom_left_radius(node_data, node_id, node_state) {
1559 let _ = write!(s,
1560 "border-bottom-left-radius: {};",
1561 p.get_css_value_fmt()
1562 );
1563 }
1564 if let Some(p) = self.get_border_bottom_right_radius(node_data, node_id, node_state) {
1565 let _ = write!(s,
1566 "border-bottom-right-radius: {};",
1567 p.get_css_value_fmt()
1568 );
1569 }
1570 if let Some(p) = self.get_opacity(node_data, node_id, node_state) {
1571 let _ = write!(s,"opacity: {};", p.get_css_value_fmt());
1572 }
1573 if let Some(p) = self.get_transform(node_data, node_id, node_state) {
1574 let _ = write!(s,"transform: {};", p.get_css_value_fmt());
1575 }
1576 if let Some(p) = self.get_transform_origin(node_data, node_id, node_state) {
1577 let _ = write!(s,"transform-origin: {};", p.get_css_value_fmt());
1578 }
1579 if let Some(p) = self.get_perspective_origin(node_data, node_id, node_state) {
1580 let _ = write!(s,"perspective-origin: {};", p.get_css_value_fmt());
1581 }
1582 if let Some(p) = self.get_backface_visibility(node_data, node_id, node_state) {
1583 let _ = write!(s,"backface-visibility: {};", p.get_css_value_fmt());
1584 }
1585 if let Some(p) = self.get_hyphens(node_data, node_id, node_state) {
1586 let _ = write!(s,"hyphens: {};", p.get_css_value_fmt());
1587 }
1588 if let Some(p) = self.get_direction(node_data, node_id, node_state) {
1589 let _ = write!(s,"direction: {};", p.get_css_value_fmt());
1590 }
1591 if let Some(p) = self.get_unicode_bidi(node_data, node_id, node_state) {
1592 let _ = write!(s,"unicode-bidi: {};", p.get_css_value_fmt());
1593 }
1594 if let Some(p) = self.get_text_box_trim(node_data, node_id, node_state) {
1595 let _ = write!(s,"text-box-trim: {};", p.get_css_value_fmt());
1596 }
1597 if let Some(p) = self.get_text_box_edge(node_data, node_id, node_state) {
1598 let _ = write!(s,"text-box-edge: {};", p.get_css_value_fmt());
1599 }
1600 if let Some(p) = self.get_dominant_baseline(node_data, node_id, node_state) {
1601 let _ = write!(s,"dominant-baseline: {};", p.get_css_value_fmt());
1602 }
1603 if let Some(p) = self.get_alignment_baseline(node_data, node_id, node_state) {
1604 let _ = write!(s,"alignment-baseline: {};", p.get_css_value_fmt());
1605 }
1606 if let Some(p) = self.get_initial_letter_align(node_data, node_id, node_state) {
1607 let _ = write!(s,"initial-letter-align: {};", p.get_css_value_fmt());
1608 }
1609 if let Some(p) = self.get_initial_letter_wrap(node_data, node_id, node_state) {
1610 let _ = write!(s,"initial-letter-wrap: {};", p.get_css_value_fmt());
1611 }
1612 if let Some(p) = self.get_scrollbar_gutter(node_data, node_id, node_state) {
1613 let _ = write!(s,"scrollbar-gutter: {};", p.get_css_value_fmt());
1614 }
1615 if let Some(p) = self.get_overflow_clip_margin(node_data, node_id, node_state) {
1616 let _ = write!(s,"overflow-clip-margin: {};", p.get_css_value_fmt());
1617 }
1618 if let Some(p) = self.get_clip(node_data, node_id, node_state) {
1619 let _ = write!(s,"clip: {};", p.get_css_value_fmt());
1620 }
1621 if let Some(p) = self.get_white_space(node_data, node_id, node_state) {
1622 let _ = write!(s,"white-space: {};", p.get_css_value_fmt());
1623 }
1624 if let Some(p) = self.get_display(node_data, node_id, node_state) {
1625 let _ = write!(s,"display: {};", p.get_css_value_fmt());
1626 }
1627 if let Some(p) = self.get_float(node_data, node_id, node_state) {
1628 let _ = write!(s,"float: {};", p.get_css_value_fmt());
1629 }
1630 if let Some(p) = self.get_box_sizing(node_data, node_id, node_state) {
1631 let _ = write!(s,"box-sizing: {};", p.get_css_value_fmt());
1632 }
1633 if let Some(p) = self.get_width(node_data, node_id, node_state) {
1634 let _ = write!(s,"width: {};", p.get_css_value_fmt());
1635 }
1636 if let Some(p) = self.get_height(node_data, node_id, node_state) {
1637 let _ = write!(s,"height: {};", p.get_css_value_fmt());
1638 }
1639 if let Some(p) = self.get_min_width(node_data, node_id, node_state) {
1640 let _ = write!(s,"min-width: {};", p.get_css_value_fmt());
1641 }
1642 if let Some(p) = self.get_min_height(node_data, node_id, node_state) {
1643 let _ = write!(s,"min-height: {};", p.get_css_value_fmt());
1644 }
1645 if let Some(p) = self.get_max_width(node_data, node_id, node_state) {
1646 let _ = write!(s,"max-width: {};", p.get_css_value_fmt());
1647 }
1648 if let Some(p) = self.get_max_height(node_data, node_id, node_state) {
1649 let _ = write!(s,"max-height: {};", p.get_css_value_fmt());
1650 }
1651 if let Some(p) = self.get_position(node_data, node_id, node_state) {
1652 let _ = write!(s,"position: {};", p.get_css_value_fmt());
1653 }
1654 if let Some(p) = self.get_top(node_data, node_id, node_state) {
1655 let _ = write!(s,"top: {};", p.get_css_value_fmt());
1656 }
1657 if let Some(p) = self.get_bottom(node_data, node_id, node_state) {
1658 let _ = write!(s,"bottom: {};", p.get_css_value_fmt());
1659 }
1660 if let Some(p) = self.get_right(node_data, node_id, node_state) {
1661 let _ = write!(s,"right: {};", p.get_css_value_fmt());
1662 }
1663 if let Some(p) = self.get_left(node_data, node_id, node_state) {
1664 let _ = write!(s,"left: {};", p.get_css_value_fmt());
1665 }
1666 if let Some(p) = self.get_padding_top(node_data, node_id, node_state) {
1667 let _ = write!(s,"padding-top: {};", p.get_css_value_fmt());
1668 }
1669 if let Some(p) = self.get_padding_bottom(node_data, node_id, node_state) {
1670 let _ = write!(s,"padding-bottom: {};", p.get_css_value_fmt());
1671 }
1672 if let Some(p) = self.get_padding_left(node_data, node_id, node_state) {
1673 let _ = write!(s,"padding-left: {};", p.get_css_value_fmt());
1674 }
1675 if let Some(p) = self.get_padding_right(node_data, node_id, node_state) {
1676 let _ = write!(s,"padding-right: {};", p.get_css_value_fmt());
1677 }
1678 if let Some(p) = self.get_margin_top(node_data, node_id, node_state) {
1679 let _ = write!(s,"margin-top: {};", p.get_css_value_fmt());
1680 }
1681 if let Some(p) = self.get_margin_bottom(node_data, node_id, node_state) {
1682 let _ = write!(s,"margin-bottom: {};", p.get_css_value_fmt());
1683 }
1684 if let Some(p) = self.get_margin_left(node_data, node_id, node_state) {
1685 let _ = write!(s,"margin-left: {};", p.get_css_value_fmt());
1686 }
1687 if let Some(p) = self.get_margin_right(node_data, node_id, node_state) {
1688 let _ = write!(s,"margin-right: {};", p.get_css_value_fmt());
1689 }
1690 if let Some(p) = self.get_border_top_width(node_data, node_id, node_state) {
1691 let _ = write!(s,"border-top-width: {};", p.get_css_value_fmt());
1692 }
1693 if let Some(p) = self.get_border_left_width(node_data, node_id, node_state) {
1694 let _ = write!(s,"border-left-width: {};", p.get_css_value_fmt());
1695 }
1696 if let Some(p) = self.get_border_right_width(node_data, node_id, node_state) {
1697 let _ = write!(s,"border-right-width: {};", p.get_css_value_fmt());
1698 }
1699 if let Some(p) = self.get_border_bottom_width(node_data, node_id, node_state) {
1700 let _ = write!(s,"border-bottom-width: {};", p.get_css_value_fmt());
1701 }
1702 if let Some(p) = self.get_overflow_x(node_data, node_id, node_state) {
1703 let _ = write!(s,"overflow-x: {};", p.get_css_value_fmt());
1704 }
1705 if let Some(p) = self.get_overflow_y(node_data, node_id, node_state) {
1706 let _ = write!(s,"overflow-y: {};", p.get_css_value_fmt());
1707 }
1708 if let Some(p) = self.get_flex_direction(node_data, node_id, node_state) {
1709 let _ = write!(s,"flex-direction: {};", p.get_css_value_fmt());
1710 }
1711 if let Some(p) = self.get_flex_wrap(node_data, node_id, node_state) {
1712 let _ = write!(s,"flex-wrap: {};", p.get_css_value_fmt());
1713 }
1714 if let Some(p) = self.get_flex_grow(node_data, node_id, node_state) {
1715 let _ = write!(s,"flex-grow: {};", p.get_css_value_fmt());
1716 }
1717 if let Some(p) = self.get_flex_shrink(node_data, node_id, node_state) {
1718 let _ = write!(s,"flex-shrink: {};", p.get_css_value_fmt());
1719 }
1720 if let Some(p) = self.get_justify_content(node_data, node_id, node_state) {
1721 let _ = write!(s,"justify-content: {};", p.get_css_value_fmt());
1722 }
1723 if let Some(p) = self.get_align_items(node_data, node_id, node_state) {
1724 let _ = write!(s,"align-items: {};", p.get_css_value_fmt());
1725 }
1726 if let Some(p) = self.get_align_content(node_data, node_id, node_state) {
1727 let _ = write!(s,"align-content: {};", p.get_css_value_fmt());
1728 }
1729 s
1730 }
1731}
1732
1733#[repr(C)]
1734#[derive(Debug, PartialEq, Clone)]
1735pub struct CssPropertyCachePtr {
1736 pub ptr: ManuallyDrop<Box<CssPropertyCache>>,
1744 pub run_destructor: bool,
1745}
1746
1747impl CssPropertyCachePtr {
1748 pub fn new(cache: CssPropertyCache) -> Self {
1749 Self {
1750 ptr: ManuallyDrop::new(Box::new(cache)),
1751 run_destructor: true,
1752 }
1753 }
1754 pub fn downcast_mut(&mut self) -> &mut CssPropertyCache {
1755 &mut self.ptr
1756 }
1757}
1758
1759impl Drop for CssPropertyCachePtr {
1760 fn drop(&mut self) {
1761 if self.run_destructor {
1764 self.run_destructor = false;
1765 unsafe {
1766 ManuallyDrop::drop(&mut self.ptr);
1767 }
1768 }
1769 }
1770}
1771
1772macro_rules! impl_get_prop {
1776 ($name:ident, $value_ty:ty, $variant:ident, $as_method:ident) => {
1777 pub fn $name<'a>(
1778 &'a self,
1779 node_data: &'a NodeData,
1780 node_id: &NodeId,
1781 node_state: &StyledNodeState,
1782 ) -> Option<&'a $value_ty> {
1783 self.get_property(node_data, node_id, node_state, &CssPropertyType::$variant)
1784 .and_then(|p| p.$as_method())
1785 }
1786 };
1787}
1788
1789impl CssPropertyCache {
1790 #[must_use] pub fn empty(node_count: usize) -> Self {
1791 Self {
1792 node_count,
1793 user_overridden_properties: Vec::new(),
1794
1795 cascaded_props: FlatVecVec::new(node_count),
1796 css_props: FlatVecVec::new(node_count),
1797
1798 computed_values: Vec::new(),
1799 compact_cache: None,
1800 global_css_props: Vec::new(),
1801 resolved_font_sizes_px: crate::sync::OnceLock::new(),
1802 }
1803 }
1804
1805 pub fn invalidate_resolved_font_sizes(&mut self) {
1811 self.resolved_font_sizes_px = crate::sync::OnceLock::new();
1812 }
1813
1814 pub fn append(&mut self, other: &mut Self) {
1815 self.user_overridden_properties.append(&mut other.user_overridden_properties);
1816 self.cascaded_props.extend_from(&mut other.cascaded_props);
1817 self.css_props.extend_from(&mut other.css_props);
1818 self.computed_values.append(&mut other.computed_values);
1819
1820 self.node_count += other.node_count;
1821 self.resolved_font_sizes_px = crate::sync::OnceLock::new();
1823
1824 self.compact_cache = None;
1826 }
1827
1828 pub fn is_horizontal_overflow_visible(
1829 &self,
1830 node_data: &NodeData,
1831 node_id: &NodeId,
1832 node_state: &StyledNodeState,
1833 ) -> bool {
1834 self.get_overflow_x(node_data, node_id, node_state)
1835 .and_then(|p| p.get_property_or_default())
1836 .unwrap_or_default()
1837 .is_overflow_visible()
1838 }
1839
1840 pub fn is_vertical_overflow_visible(
1841 &self,
1842 node_data: &NodeData,
1843 node_id: &NodeId,
1844 node_state: &StyledNodeState,
1845 ) -> bool {
1846 self.get_overflow_y(node_data, node_id, node_state)
1847 .and_then(|p| p.get_property_or_default())
1848 .unwrap_or_default()
1849 .is_overflow_visible()
1850 }
1851
1852 pub fn is_horizontal_overflow_hidden(
1853 &self,
1854 node_data: &NodeData,
1855 node_id: &NodeId,
1856 node_state: &StyledNodeState,
1857 ) -> bool {
1858 self.get_overflow_x(node_data, node_id, node_state)
1859 .and_then(|p| p.get_property_or_default())
1860 .unwrap_or_default()
1861 .is_overflow_hidden()
1862 }
1863
1864 pub fn is_vertical_overflow_hidden(
1865 &self,
1866 node_data: &NodeData,
1867 node_id: &NodeId,
1868 node_state: &StyledNodeState,
1869 ) -> bool {
1870 self.get_overflow_y(node_data, node_id, node_state)
1871 .and_then(|p| p.get_property_or_default())
1872 .unwrap_or_default()
1873 .is_overflow_hidden()
1874 }
1875
1876 pub fn get_text_color_or_default(
1877 &self,
1878 node_data: &NodeData,
1879 node_id: &NodeId,
1880 node_state: &StyledNodeState,
1881 ) -> StyleTextColor {
1882 use azul_css::defaults::DEFAULT_TEXT_COLOR;
1883 self.get_text_color(node_data, node_id, node_state)
1884 .and_then(|fs| fs.get_property().copied())
1885 .unwrap_or(DEFAULT_TEXT_COLOR)
1886 }
1887
1888 pub fn get_font_id_or_default(
1890 &self,
1891 node_data: &NodeData,
1892 node_id: &NodeId,
1893 node_state: &StyledNodeState,
1894 ) -> StyleFontFamilyVec {
1895 use azul_css::defaults::DEFAULT_FONT_ID;
1896 let default_font_id = vec![StyleFontFamily::System(AzString::from_const_str(
1897 DEFAULT_FONT_ID,
1898 ))]
1899 .into();
1900 let font_family_opt = self.get_font_family(node_data, node_id, node_state);
1901
1902 font_family_opt
1903 .as_ref()
1904 .and_then(|family| Some(family.get_property()?.clone()))
1905 .unwrap_or(default_font_id)
1906 }
1907
1908 pub fn get_font_size_or_default(
1909 &self,
1910 node_data: &NodeData,
1911 node_id: &NodeId,
1912 node_state: &StyledNodeState,
1913 ) -> StyleFontSize {
1914 use azul_css::defaults::DEFAULT_FONT_SIZE;
1915 self.get_font_size(node_data, node_id, node_state)
1916 .and_then(|fs| fs.get_property().copied())
1917 .unwrap_or(DEFAULT_FONT_SIZE)
1918 }
1919
1920 pub fn has_border(
1921 &self,
1922 node_data: &NodeData,
1923 node_id: &NodeId,
1924 node_state: &StyledNodeState,
1925 ) -> bool {
1926 self.get_border_left_width(node_data, node_id, node_state)
1927 .is_some()
1928 || self
1929 .get_border_right_width(node_data, node_id, node_state)
1930 .is_some()
1931 || self
1932 .get_border_top_width(node_data, node_id, node_state)
1933 .is_some()
1934 || self
1935 .get_border_bottom_width(node_data, node_id, node_state)
1936 .is_some()
1937 }
1938
1939 pub fn has_box_shadow(
1940 &self,
1941 node_data: &NodeData,
1942 node_id: &NodeId,
1943 node_state: &StyledNodeState,
1944 ) -> bool {
1945 self.get_box_shadow_left(node_data, node_id, node_state)
1946 .is_some()
1947 || self
1948 .get_box_shadow_right(node_data, node_id, node_state)
1949 .is_some()
1950 || self
1951 .get_box_shadow_top(node_data, node_id, node_state)
1952 .is_some()
1953 || self
1954 .get_box_shadow_bottom(node_data, node_id, node_state)
1955 .is_some()
1956 }
1957
1958 pub fn get_property<'a>(
1959 &'a self,
1960 node_data: &'a NodeData,
1961 node_id: &NodeId,
1962 node_state: &StyledNodeState,
1963 css_property_type: &CssPropertyType,
1964 ) -> Option<&'a CssProperty> {
1965 #[cfg(feature = "std")]
1982 {
1983 static PROP_COUNT_ENABLED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1984 let enabled = *PROP_COUNT_ENABLED.get_or_init(crate::profile::cascade_enabled);
1985 if enabled {
1986 let _ = PROP_COUNTS.try_with(|c| {
1993 *c.borrow_mut()
1994 .entry(Self::css_prop_type_label(css_property_type))
1995 .or_insert(0) += 1;
1996 });
1997 }
1998 }
1999
2000 self.get_property_slow(node_data, node_id, node_state, css_property_type)
2004 }
2005
2006 #[cfg(feature = "std")]
2007 #[allow(clippy::trivially_copy_pass_by_ref)] fn css_prop_type_label(t: &CssPropertyType) -> &'static str {
2009 use std::sync::{Mutex, OnceLock};
2014 static TABLE: OnceLock<Mutex<std::collections::HashMap<CssPropertyType, &'static str>>> =
2015 OnceLock::new();
2016 let m = TABLE.get_or_init(|| Mutex::new(std::collections::HashMap::new()));
2017 let mut g = m.lock().expect("AZ_PROP_COUNT label table poisoned");
2018 if let Some(s) = g.get(t) {
2019 return s;
2020 }
2021 let s: String = std::format!("{t:?}");
2022 let leaked: &'static str = std::boxed::Box::leak(s.into_boxed_str());
2023 g.insert(*t, leaked);
2024 leaked
2025 }
2026
2027 #[allow(clippy::trivially_copy_pass_by_ref)] #[allow(clippy::too_many_lines)] pub(crate) fn get_property_slow<'a>(
2033 &'a self,
2034 node_data: &'a NodeData,
2035 node_id: &NodeId,
2036 node_state: &StyledNodeState,
2037 css_property_type: &CssPropertyType,
2038 ) -> Option<&'a CssProperty> {
2039
2040 use azul_css::dynamic_selector::{DynamicSelector, PseudoStateType};
2041
2042 fn matches_pseudo_state(
2046 conds: &azul_css::dynamic_selector::DynamicSelectorVec,
2047 state: PseudoStateType,
2048 ) -> bool {
2049 let conditions = conds.as_slice();
2050 if conditions.is_empty() {
2051 state == PseudoStateType::Normal
2052 } else {
2053 conditions
2054 .iter()
2055 .all(|c| matches!(c, DynamicSelector::PseudoState(s) if *s == state))
2056 }
2057 }
2058
2059 if let Some(v) = self.user_overridden_properties.get(node_id.index()) {
2061 if let Ok(idx) = v.binary_search_by_key(css_property_type, |(k, _)| *k) {
2062 return Some(&v[idx].1);
2063 }
2064 }
2065
2066 if node_state.focused {
2069 if let Some(p) = node_data.style.iter_inline_properties().find_map(|(prop, conds)| {
2071 if matches_pseudo_state(conds,PseudoStateType::Focus)
2072 && prop.get_type() == *css_property_type
2073 {
2074 Some(prop)
2075 } else {
2076 None
2077 }
2078 }) {
2079 return Some(p);
2080 }
2081
2082 if let Some(p) = Self::find_in_stateful(
2084 self.css_props.get_slice(node_id.index()),
2085 PseudoStateType::Focus,
2086 css_property_type,
2087 ) {
2088 return Some(p);
2089 }
2090
2091 if let Some(p) = Self::find_in_stateful(
2093 self.cascaded_props.get_slice(node_id.index()),
2094 PseudoStateType::Focus,
2095 css_property_type,
2096 ) {
2097 return Some(p);
2098 }
2099 }
2100
2101 if node_state.active {
2102 if let Some(p) = node_data.style.iter_inline_properties().find_map(|(prop, conds)| {
2104 if matches_pseudo_state(conds,PseudoStateType::Active)
2105 && prop.get_type() == *css_property_type
2106 {
2107 Some(prop)
2108 } else {
2109 None
2110 }
2111 }) {
2112 return Some(p);
2113 }
2114
2115 if let Some(p) = Self::find_in_stateful(
2117 self.css_props.get_slice(node_id.index()),
2118 PseudoStateType::Active,
2119 css_property_type,
2120 ) {
2121 return Some(p);
2122 }
2123
2124 if let Some(p) = Self::find_in_stateful(
2126 self.cascaded_props.get_slice(node_id.index()),
2127 PseudoStateType::Active,
2128 css_property_type,
2129 ) {
2130 return Some(p);
2131 }
2132 }
2133
2134 if node_state.dragging {
2136 if let Some(p) = node_data.style.iter_inline_properties().find_map(|(prop, conds)| {
2137 if matches_pseudo_state(conds,PseudoStateType::Dragging)
2138 && prop.get_type() == *css_property_type
2139 {
2140 Some(prop)
2141 } else {
2142 None
2143 }
2144 }) {
2145 return Some(p);
2146 }
2147
2148 if let Some(p) = Self::find_in_stateful(
2149 self.css_props.get_slice(node_id.index()),
2150 PseudoStateType::Dragging,
2151 css_property_type,
2152 ) {
2153 return Some(p);
2154 }
2155
2156 if let Some(p) = Self::find_in_stateful(
2157 self.cascaded_props.get_slice(node_id.index()),
2158 PseudoStateType::Dragging,
2159 css_property_type,
2160 ) {
2161 return Some(p);
2162 }
2163 }
2164
2165 if node_state.drag_over {
2167 if let Some(p) = node_data.style.iter_inline_properties().find_map(|(prop, conds)| {
2168 if matches_pseudo_state(conds,PseudoStateType::DragOver)
2169 && prop.get_type() == *css_property_type
2170 {
2171 Some(prop)
2172 } else {
2173 None
2174 }
2175 }) {
2176 return Some(p);
2177 }
2178
2179 if let Some(p) = Self::find_in_stateful(
2180 self.css_props.get_slice(node_id.index()),
2181 PseudoStateType::DragOver,
2182 css_property_type,
2183 ) {
2184 return Some(p);
2185 }
2186
2187 if let Some(p) = Self::find_in_stateful(
2188 self.cascaded_props.get_slice(node_id.index()),
2189 PseudoStateType::DragOver,
2190 css_property_type,
2191 ) {
2192 return Some(p);
2193 }
2194 }
2195
2196 if node_state.hover {
2197 if let Some(p) = node_data.style.iter_inline_properties().find_map(|(prop, conds)| {
2199 if matches_pseudo_state(conds,PseudoStateType::Hover)
2200 && prop.get_type() == *css_property_type
2201 {
2202 Some(prop)
2203 } else {
2204 None
2205 }
2206 }) {
2207 return Some(p);
2208 }
2209
2210 if let Some(p) = Self::find_in_stateful(
2212 self.css_props.get_slice(node_id.index()),
2213 PseudoStateType::Hover,
2214 css_property_type,
2215 ) {
2216 return Some(p);
2217 }
2218
2219 if let Some(p) = Self::find_in_stateful(
2221 self.cascaded_props.get_slice(node_id.index()),
2222 PseudoStateType::Hover,
2223 css_property_type,
2224 ) {
2225 return Some(p);
2226 }
2227 }
2228
2229 if let Some(p) = node_data.style.iter_inline_properties().find_map(|(prop, conds)| {
2232 if matches_pseudo_state(conds, PseudoStateType::Normal)
2233 && prop.get_type() == *css_property_type
2234 {
2235 Some(prop)
2236 } else {
2237 None
2238 }
2239 }) {
2240 return Some(p);
2241 }
2242
2243 if let Some(p) = Self::find_in_stateful(
2245 self.css_props.get_slice(node_id.index()),
2246 PseudoStateType::Normal,
2247 css_property_type,
2248 ) {
2249 return Some(p);
2250 }
2251
2252 if let Some(p) = self.global_css_props.iter().find(|p| p.get_type() == *css_property_type) {
2256 return Some(p);
2257 }
2258
2259 if let Some(p) = Self::find_in_stateful(
2261 self.cascaded_props.get_slice(node_id.index()),
2262 PseudoStateType::Normal,
2263 css_property_type,
2264 ) {
2265 return Some(p);
2266 }
2267
2268 if css_property_type.is_inheritable() {
2271 if let Some(vec) = self.computed_values.get(node_id.index()) {
2272 if let Ok(idx) = vec.binary_search_by_key(css_property_type, |(k, _)| *k) {
2273 return Some(&vec[idx].1.property);
2274 }
2275 }
2276 }
2277
2278 crate::ua_css::get_ua_property(&node_data.node_type, *css_property_type)
2281 }
2282
2283 #[allow(clippy::trivially_copy_pass_by_ref)] pub(crate) fn get_property_with_context<'a>(
2292 &'a self,
2293 node_data: &'a NodeData,
2294 node_id: &NodeId,
2295 context: &DynamicSelectorContext,
2296 css_property_type: &CssPropertyType,
2297 ) -> Option<&'a CssProperty> {
2298 if let Some(v) = self.user_overridden_properties.get(node_id.index()) {
2300 if let Ok(idx) = v.binary_search_by_key(css_property_type, |(k, _)| *k) {
2301 return Some(&v[idx].1);
2302 }
2303 }
2304
2305 let mut last_inline = None;
2313 for (prop, conds) in node_data.style.iter_inline_properties() {
2314 let conditions_match = conds.as_slice().iter().all(|c| c.matches(context));
2315 if prop.get_type() == *css_property_type && conditions_match {
2316 last_inline = Some(prop);
2317 }
2318 }
2319 if let Some(prop) = last_inline {
2320 return Some(prop);
2321 }
2322
2323 let legacy_state = StyledNodeState::from_pseudo_state_flags(&context.pseudo_state);
2325 if let Some(p) = self.get_property(node_data, node_id, &legacy_state, css_property_type) {
2326 return Some(p);
2327 }
2328
2329 None
2330 }
2331
2332 pub(crate) fn check_properties_changed(
2335 node_data: &NodeData,
2336 old_context: &DynamicSelectorContext,
2337 new_context: &DynamicSelectorContext,
2338 ) -> bool {
2339 for (_prop, conds) in node_data.style.iter_inline_properties() {
2340 let was_active = conds.as_slice().iter().all(|c| c.matches(old_context));
2341 let is_active = conds.as_slice().iter().all(|c| c.matches(new_context));
2342 if was_active != is_active {
2343 return true;
2344 }
2345 }
2346 false
2347 }
2348
2349 pub(crate) fn check_layout_properties_changed(
2352 node_data: &NodeData,
2353 old_context: &DynamicSelectorContext,
2354 new_context: &DynamicSelectorContext,
2355 ) -> bool {
2356 for (prop, conds) in node_data.style.iter_inline_properties() {
2357 if !prop.get_type().can_trigger_relayout() {
2359 continue;
2360 }
2361
2362 let was_active = conds.as_slice().iter().all(|c| c.matches(old_context));
2363 let is_active = conds.as_slice().iter().all(|c| c.matches(new_context));
2364 if was_active != is_active {
2365 return true;
2366 }
2367 }
2368 false
2369 }
2370
2371 impl_get_prop!(get_background_content, StyleBackgroundContentVecValue, BackgroundContent, as_background_content);
2372
2373 impl_get_prop!(get_hyphens, StyleHyphensValue, Hyphens, as_hyphens);
2374
2375 impl_get_prop!(get_word_break, StyleWordBreakValue, WordBreak, as_word_break);
2376
2377 impl_get_prop!(get_overflow_wrap, StyleOverflowWrapValue, OverflowWrap, as_overflow_wrap);
2378
2379 impl_get_prop!(get_line_break, StyleLineBreakValue, LineBreak, as_line_break);
2380
2381 impl_get_prop!(get_text_align_last, StyleTextAlignLastValue, TextAlignLast, as_text_align_last);
2382
2383 impl_get_prop!(get_text_transform, StyleTextTransformValue, TextTransform, as_text_transform);
2384
2385 impl_get_prop!(get_object_fit, StyleObjectFitValue, ObjectFit, as_object_fit);
2386
2387 impl_get_prop!(get_text_orientation, StyleTextOrientationValue, TextOrientation, as_text_orientation);
2388
2389 impl_get_prop!(get_object_position, StyleObjectPositionValue, ObjectPosition, as_object_position);
2390
2391 impl_get_prop!(get_aspect_ratio, StyleAspectRatioValue, AspectRatio, as_aspect_ratio);
2392
2393 impl_get_prop!(get_direction, StyleDirectionValue, Direction, as_direction);
2394
2395 impl_get_prop!(get_unicode_bidi, StyleUnicodeBidiValue, UnicodeBidi, as_unicode_bidi);
2396
2397 impl_get_prop!(get_text_box_trim, StyleTextBoxTrimValue, TextBoxTrim, as_text_box_trim);
2398
2399 impl_get_prop!(get_text_box_edge, StyleTextBoxEdgeValue, TextBoxEdge, as_text_box_edge);
2400
2401 impl_get_prop!(get_dominant_baseline, StyleDominantBaselineValue, DominantBaseline, as_dominant_baseline);
2402
2403 impl_get_prop!(get_alignment_baseline, StyleAlignmentBaselineValue, AlignmentBaseline, as_alignment_baseline);
2404
2405 impl_get_prop!(get_initial_letter_align, StyleInitialLetterAlignValue, InitialLetterAlign, as_initial_letter_align);
2406
2407 impl_get_prop!(get_initial_letter_wrap, StyleInitialLetterWrapValue, InitialLetterWrap, as_initial_letter_wrap);
2408
2409 impl_get_prop!(get_scrollbar_gutter, StyleScrollbarGutterValue, ScrollbarGutter, as_scrollbar_gutter);
2410
2411 impl_get_prop!(get_overflow_clip_margin, StyleOverflowClipMarginValue, OverflowClipMargin, as_overflow_clip_margin);
2412
2413 impl_get_prop!(get_clip, StyleClipRectValue, Clip, as_clip);
2414
2415 impl_get_prop!(get_white_space, StyleWhiteSpaceValue, WhiteSpace, as_white_space);
2416 impl_get_prop!(get_background_position, StyleBackgroundPositionVecValue, BackgroundPosition, as_background_position);
2417 impl_get_prop!(get_background_size, StyleBackgroundSizeVecValue, BackgroundSize, as_background_size);
2418 impl_get_prop!(get_background_repeat, StyleBackgroundRepeatVecValue, BackgroundRepeat, as_background_repeat);
2419 impl_get_prop!(get_font_size, StyleFontSizeValue, FontSize, as_font_size);
2420 impl_get_prop!(get_font_family, StyleFontFamilyVecValue, FontFamily, as_font_family);
2421 impl_get_prop!(get_font_weight, StyleFontWeightValue, FontWeight, as_font_weight);
2422 impl_get_prop!(get_font_style, StyleFontStyleValue, FontStyle, as_font_style);
2423 impl_get_prop!(get_text_color, StyleTextColorValue, TextColor, as_text_color);
2424 impl_get_prop!(get_text_indent, StyleTextIndentValue, TextIndent, as_text_indent);
2425 impl_get_prop!(get_initial_letter, StyleInitialLetterValue, InitialLetter, as_initial_letter);
2426 impl_get_prop!(get_line_clamp, StyleLineClampValue, LineClamp, as_line_clamp);
2427 impl_get_prop!(get_hanging_punctuation, StyleHangingPunctuationValue, HangingPunctuation, as_hanging_punctuation);
2428 impl_get_prop!(get_text_combine_upright, StyleTextCombineUprightValue, TextCombineUpright, as_text_combine_upright);
2429 impl_get_prop!(get_exclusion_margin, StyleExclusionMarginValue, ExclusionMargin, as_exclusion_margin);
2430 impl_get_prop!(get_hyphenation_language, StyleHyphenationLanguageValue, HyphenationLanguage, as_hyphenation_language);
2431 impl_get_prop!(get_caret_color, CaretColorValue, CaretColor, as_caret_color);
2432
2433 impl_get_prop!(get_caret_width, CaretWidthValue, CaretWidth, as_caret_width);
2434
2435 impl_get_prop!(get_caret_animation_duration, CaretAnimationDurationValue, CaretAnimationDuration, as_caret_animation_duration);
2436
2437 impl_get_prop!(get_selection_background_color, SelectionBackgroundColorValue, SelectionBackgroundColor, as_selection_background_color);
2438
2439 impl_get_prop!(get_selection_color, SelectionColorValue, SelectionColor, as_selection_color);
2440
2441 impl_get_prop!(get_selection_radius, SelectionRadiusValue, SelectionRadius, as_selection_radius);
2442
2443 impl_get_prop!(get_text_justify, LayoutTextJustifyValue, TextJustify, as_text_justify);
2444
2445 impl_get_prop!(get_z_index, LayoutZIndexValue, ZIndex, as_z_index);
2446
2447 impl_get_prop!(get_flex_basis, LayoutFlexBasisValue, FlexBasis, as_flex_basis);
2448
2449 impl_get_prop!(get_column_gap, LayoutColumnGapValue, ColumnGap, as_column_gap);
2450
2451 impl_get_prop!(get_row_gap, LayoutRowGapValue, RowGap, as_row_gap);
2452
2453 impl_get_prop!(get_grid_template_columns, LayoutGridTemplateColumnsValue, GridTemplateColumns, as_grid_template_columns);
2454
2455 impl_get_prop!(get_grid_template_rows, LayoutGridTemplateRowsValue, GridTemplateRows, as_grid_template_rows);
2456
2457 impl_get_prop!(get_grid_auto_columns, LayoutGridAutoColumnsValue, GridAutoColumns, as_grid_auto_columns);
2458
2459 impl_get_prop!(get_grid_auto_rows, LayoutGridAutoRowsValue, GridAutoRows, as_grid_auto_rows);
2460
2461 impl_get_prop!(get_grid_column, LayoutGridColumnValue, GridColumn, as_grid_column);
2462
2463 impl_get_prop!(get_grid_row, LayoutGridRowValue, GridRow, as_grid_row);
2464
2465 impl_get_prop!(get_grid_auto_flow, LayoutGridAutoFlowValue, GridAutoFlow, as_grid_auto_flow);
2466
2467 impl_get_prop!(get_justify_self, LayoutJustifySelfValue, JustifySelf, as_justify_self);
2468
2469 impl_get_prop!(get_justify_items, LayoutJustifyItemsValue, JustifyItems, as_justify_items);
2470
2471 impl_get_prop!(get_gap, LayoutGapValue, Gap, as_gap);
2472
2473 #[allow(clippy::trivially_copy_pass_by_ref)] pub(crate) fn get_grid_gap<'a>(
2476 &'a self,
2477 node_data: &'a NodeData,
2478 node_id: &NodeId,
2479 node_state: &StyledNodeState,
2480 ) -> Option<&'a LayoutGapValue> {
2481 self.get_property(node_data, node_id, node_state, &CssPropertyType::GridGap)
2482 .and_then(|p| p.as_grid_gap())
2483 }
2484
2485 impl_get_prop!(get_align_self, LayoutAlignSelfValue, AlignSelf, as_align_self);
2486
2487 impl_get_prop!(get_font, StyleFontValue, Font, as_font);
2488
2489 impl_get_prop!(get_writing_mode, LayoutWritingModeValue, WritingMode, as_writing_mode);
2490
2491 impl_get_prop!(get_clear, LayoutClearValue, Clear, as_clear);
2492
2493 impl_get_prop!(get_shape_outside, ShapeOutsideValue, ShapeOutside, as_shape_outside);
2494
2495 impl_get_prop!(get_shape_inside, ShapeInsideValue, ShapeInside, as_shape_inside);
2496
2497 impl_get_prop!(get_clip_path, ClipPathValue, ClipPath, as_clip_path);
2498
2499 pub fn get_scrollbar_track<'a>(
2501 &'a self,
2502 node_data: &'a NodeData,
2503 node_id: &NodeId,
2504 node_state: &StyledNodeState,
2505 ) -> Option<&'a StyleBackgroundContentValue> {
2506 self.get_property(node_data, node_id, node_state, &CssPropertyType::ScrollbarTrack)
2507 .and_then(|p| p.as_scrollbar_track())
2508 }
2509
2510 pub fn get_scrollbar_thumb<'a>(
2512 &'a self,
2513 node_data: &'a NodeData,
2514 node_id: &NodeId,
2515 node_state: &StyledNodeState,
2516 ) -> Option<&'a StyleBackgroundContentValue> {
2517 self.get_property(node_data, node_id, node_state, &CssPropertyType::ScrollbarThumb)
2518 .and_then(|p| p.as_scrollbar_thumb())
2519 }
2520
2521 pub fn get_scrollbar_button<'a>(
2523 &'a self,
2524 node_data: &'a NodeData,
2525 node_id: &NodeId,
2526 node_state: &StyledNodeState,
2527 ) -> Option<&'a StyleBackgroundContentValue> {
2528 self.get_property(node_data, node_id, node_state, &CssPropertyType::ScrollbarButton)
2529 .and_then(|p| p.as_scrollbar_button())
2530 }
2531
2532 pub fn get_scrollbar_corner<'a>(
2534 &'a self,
2535 node_data: &'a NodeData,
2536 node_id: &NodeId,
2537 node_state: &StyledNodeState,
2538 ) -> Option<&'a StyleBackgroundContentValue> {
2539 self.get_property(node_data, node_id, node_state, &CssPropertyType::ScrollbarCorner)
2540 .and_then(|p| p.as_scrollbar_corner())
2541 }
2542
2543 pub fn get_scrollbar_resizer<'a>(
2545 &'a self,
2546 node_data: &'a NodeData,
2547 node_id: &NodeId,
2548 node_state: &StyledNodeState,
2549 ) -> Option<&'a StyleBackgroundContentValue> {
2550 self.get_property(node_data, node_id, node_state, &CssPropertyType::ScrollbarResizer)
2551 .and_then(|p| p.as_scrollbar_resizer())
2552 }
2553
2554 impl_get_prop!(get_scrollbar_width, LayoutScrollbarWidthValue, ScrollbarWidth, as_scrollbar_width);
2555
2556 impl_get_prop!(get_scrollbar_color, StyleScrollbarColorValue, ScrollbarColor, as_scrollbar_color);
2557
2558 impl_get_prop!(get_scrollbar_visibility, ScrollbarVisibilityModeValue, ScrollbarVisibility, as_scrollbar_visibility);
2559
2560 impl_get_prop!(get_scrollbar_fade_delay, ScrollbarFadeDelayValue, ScrollbarFadeDelay, as_scrollbar_fade_delay);
2561
2562 impl_get_prop!(get_scrollbar_fade_duration, ScrollbarFadeDurationValue, ScrollbarFadeDuration, as_scrollbar_fade_duration);
2563
2564 impl_get_prop!(get_visibility, StyleVisibilityValue, Visibility, as_visibility);
2565
2566 impl_get_prop!(get_break_before, PageBreakValue, BreakBefore, as_break_before);
2567
2568 impl_get_prop!(get_break_after, PageBreakValue, BreakAfter, as_break_after);
2569
2570 impl_get_prop!(get_break_inside, BreakInsideValue, BreakInside, as_break_inside);
2571
2572 impl_get_prop!(get_orphans, OrphansValue, Orphans, as_orphans);
2573
2574 impl_get_prop!(get_widows, WidowsValue, Widows, as_widows);
2575
2576 impl_get_prop!(get_box_decoration_break, BoxDecorationBreakValue, BoxDecorationBreak, as_box_decoration_break);
2577
2578 impl_get_prop!(get_column_count, ColumnCountValue, ColumnCount, as_column_count);
2579
2580 impl_get_prop!(get_column_width, ColumnWidthValue, ColumnWidth, as_column_width);
2581
2582 impl_get_prop!(get_column_span, ColumnSpanValue, ColumnSpan, as_column_span);
2583
2584 impl_get_prop!(get_column_fill, ColumnFillValue, ColumnFill, as_column_fill);
2585
2586 impl_get_prop!(get_column_rule_width, ColumnRuleWidthValue, ColumnRuleWidth, as_column_rule_width);
2587
2588 impl_get_prop!(get_column_rule_style, ColumnRuleStyleValue, ColumnRuleStyle, as_column_rule_style);
2589
2590 impl_get_prop!(get_column_rule_color, ColumnRuleColorValue, ColumnRuleColor, as_column_rule_color);
2591
2592 impl_get_prop!(get_flow_into, FlowIntoValue, FlowInto, as_flow_into);
2593
2594 impl_get_prop!(get_flow_from, FlowFromValue, FlowFrom, as_flow_from);
2595
2596 impl_get_prop!(get_shape_margin, ShapeMarginValue, ShapeMargin, as_shape_margin);
2597
2598 impl_get_prop!(get_shape_image_threshold, ShapeImageThresholdValue, ShapeImageThreshold, as_shape_image_threshold);
2599
2600 impl_get_prop!(get_content, ContentValue, Content, as_content);
2601
2602 impl_get_prop!(get_counter_reset, CounterResetValue, CounterReset, as_counter_reset);
2603
2604 impl_get_prop!(get_counter_increment, CounterIncrementValue, CounterIncrement, as_counter_increment);
2605
2606 impl_get_prop!(get_string_set, StringSetValue, StringSet, as_string_set);
2607 impl_get_prop!(get_text_align, StyleTextAlignValue, TextAlign, as_text_align);
2608 impl_get_prop!(get_user_select, StyleUserSelectValue, UserSelect, as_user_select);
2609 impl_get_prop!(get_text_decoration, StyleTextDecorationValue, TextDecoration, as_text_decoration);
2610 impl_get_prop!(get_vertical_align, StyleVerticalAlignValue, VerticalAlign, as_vertical_align);
2611 impl_get_prop!(get_line_height, StyleLineHeightValue, LineHeight, as_line_height);
2612 impl_get_prop!(get_letter_spacing, StyleLetterSpacingValue, LetterSpacing, as_letter_spacing);
2613 impl_get_prop!(get_word_spacing, StyleWordSpacingValue, WordSpacing, as_word_spacing);
2614 impl_get_prop!(get_tab_size, StyleTabSizeValue, TabSize, as_tab_size);
2615 impl_get_prop!(get_cursor, StyleCursorValue, Cursor, as_cursor);
2616 impl_get_prop!(get_box_shadow_left, StyleBoxShadowValue, BoxShadowLeft, as_box_shadow_left);
2617 impl_get_prop!(get_box_shadow_right, StyleBoxShadowValue, BoxShadowRight, as_box_shadow_right);
2618 impl_get_prop!(get_box_shadow_top, StyleBoxShadowValue, BoxShadowTop, as_box_shadow_top);
2619 impl_get_prop!(get_box_shadow_bottom, StyleBoxShadowValue, BoxShadowBottom, as_box_shadow_bottom);
2620 impl_get_prop!(get_border_top_color, StyleBorderTopColorValue, BorderTopColor, as_border_top_color);
2621 impl_get_prop!(get_border_left_color, StyleBorderLeftColorValue, BorderLeftColor, as_border_left_color);
2622 impl_get_prop!(get_border_right_color, StyleBorderRightColorValue, BorderRightColor, as_border_right_color);
2623 impl_get_prop!(get_border_bottom_color, StyleBorderBottomColorValue, BorderBottomColor, as_border_bottom_color);
2624 impl_get_prop!(get_border_top_style, StyleBorderTopStyleValue, BorderTopStyle, as_border_top_style);
2625 impl_get_prop!(get_border_left_style, StyleBorderLeftStyleValue, BorderLeftStyle, as_border_left_style);
2626 impl_get_prop!(get_border_right_style, StyleBorderRightStyleValue, BorderRightStyle, as_border_right_style);
2627 impl_get_prop!(get_border_bottom_style, StyleBorderBottomStyleValue, BorderBottomStyle, as_border_bottom_style);
2628 impl_get_prop!(get_border_top_left_radius, StyleBorderTopLeftRadiusValue, BorderTopLeftRadius, as_border_top_left_radius);
2629 impl_get_prop!(get_border_top_right_radius, StyleBorderTopRightRadiusValue, BorderTopRightRadius, as_border_top_right_radius);
2630 impl_get_prop!(get_border_bottom_left_radius, StyleBorderBottomLeftRadiusValue, BorderBottomLeftRadius, as_border_bottom_left_radius);
2631 impl_get_prop!(get_border_bottom_right_radius, StyleBorderBottomRightRadiusValue, BorderBottomRightRadius, as_border_bottom_right_radius);
2632 impl_get_prop!(get_opacity, StyleOpacityValue, Opacity, as_opacity);
2633 impl_get_prop!(get_transform, StyleTransformVecValue, Transform, as_transform);
2634 impl_get_prop!(get_transform_origin, StyleTransformOriginValue, TransformOrigin, as_transform_origin);
2635 impl_get_prop!(get_perspective_origin, StylePerspectiveOriginValue, PerspectiveOrigin, as_perspective_origin);
2636 impl_get_prop!(get_backface_visibility, StyleBackfaceVisibilityValue, BackfaceVisibility, as_backface_visibility);
2637 impl_get_prop!(get_display, LayoutDisplayValue, Display, as_display);
2638 impl_get_prop!(get_float, LayoutFloatValue, Float, as_float);
2639 impl_get_prop!(get_box_sizing, LayoutBoxSizingValue, BoxSizing, as_box_sizing);
2640 impl_get_prop!(get_width, LayoutWidthValue, Width, as_width);
2641 impl_get_prop!(get_height, LayoutHeightValue, Height, as_height);
2642 impl_get_prop!(get_min_width, LayoutMinWidthValue, MinWidth, as_min_width);
2643 impl_get_prop!(get_min_height, LayoutMinHeightValue, MinHeight, as_min_height);
2644 impl_get_prop!(get_max_width, LayoutMaxWidthValue, MaxWidth, as_max_width);
2645 impl_get_prop!(get_max_height, LayoutMaxHeightValue, MaxHeight, as_max_height);
2646 impl_get_prop!(get_position, LayoutPositionValue, Position, as_position);
2647 impl_get_prop!(get_top, LayoutTopValue, Top, as_top);
2648 impl_get_prop!(get_bottom, LayoutInsetBottomValue, Bottom, as_bottom);
2649 impl_get_prop!(get_right, LayoutRightValue, Right, as_right);
2650 impl_get_prop!(get_left, LayoutLeftValue, Left, as_left);
2651 impl_get_prop!(get_padding_top, LayoutPaddingTopValue, PaddingTop, as_padding_top);
2652 impl_get_prop!(get_padding_bottom, LayoutPaddingBottomValue, PaddingBottom, as_padding_bottom);
2653 impl_get_prop!(get_padding_left, LayoutPaddingLeftValue, PaddingLeft, as_padding_left);
2654 impl_get_prop!(get_padding_right, LayoutPaddingRightValue, PaddingRight, as_padding_right);
2655 impl_get_prop!(get_margin_top, LayoutMarginTopValue, MarginTop, as_margin_top);
2656 impl_get_prop!(get_margin_bottom, LayoutMarginBottomValue, MarginBottom, as_margin_bottom);
2657 impl_get_prop!(get_margin_left, LayoutMarginLeftValue, MarginLeft, as_margin_left);
2658 impl_get_prop!(get_margin_right, LayoutMarginRightValue, MarginRight, as_margin_right);
2659 impl_get_prop!(get_border_top_width, LayoutBorderTopWidthValue, BorderTopWidth, as_border_top_width);
2660 impl_get_prop!(get_border_left_width, LayoutBorderLeftWidthValue, BorderLeftWidth, as_border_left_width);
2661 impl_get_prop!(get_border_right_width, LayoutBorderRightWidthValue, BorderRightWidth, as_border_right_width);
2662 impl_get_prop!(get_border_bottom_width, LayoutBorderBottomWidthValue, BorderBottomWidth, as_border_bottom_width);
2663 impl_get_prop!(get_overflow_x, LayoutOverflowValue, OverflowX, as_overflow_x);
2664 impl_get_prop!(get_overflow_y, LayoutOverflowValue, OverflowY, as_overflow_y);
2665 impl_get_prop!(get_overflow_block, LayoutOverflowValue, OverflowBlock, as_overflow_block);
2666 impl_get_prop!(get_overflow_inline, LayoutOverflowValue, OverflowInline, as_overflow_inline);
2667 impl_get_prop!(get_flex_direction, LayoutFlexDirectionValue, FlexDirection, as_flex_direction);
2668 impl_get_prop!(get_flex_wrap, LayoutFlexWrapValue, FlexWrap, as_flex_wrap);
2669 impl_get_prop!(get_flex_grow, LayoutFlexGrowValue, FlexGrow, as_flex_grow);
2670 impl_get_prop!(get_flex_shrink, LayoutFlexShrinkValue, FlexShrink, as_flex_shrink);
2671 impl_get_prop!(get_justify_content, LayoutJustifyContentValue, JustifyContent, as_justify_content);
2672 impl_get_prop!(get_align_items, LayoutAlignItemsValue, AlignItems, as_align_items);
2673 impl_get_prop!(get_align_content, LayoutAlignContentValue, AlignContent, as_align_content);
2674 impl_get_prop!(get_mix_blend_mode, StyleMixBlendModeValue, MixBlendMode, as_mix_blend_mode);
2675 impl_get_prop!(get_filter, StyleFilterVecValue, Filter, as_filter);
2676 impl_get_prop!(get_backdrop_filter, StyleFilterVecValue, BackdropFilter, as_backdrop_filter);
2677 impl_get_prop!(get_text_shadow, StyleBoxShadowValue, TextShadow, as_text_shadow);
2678 impl_get_prop!(get_list_style_type, StyleListStyleTypeValue, ListStyleType, as_list_style_type);
2679 impl_get_prop!(get_list_style_position, StyleListStylePositionValue, ListStylePosition, as_list_style_position);
2680 impl_get_prop!(get_table_layout, LayoutTableLayoutValue, TableLayout, as_table_layout);
2681 impl_get_prop!(get_border_collapse, StyleBorderCollapseValue, BorderCollapse, as_border_collapse);
2682 impl_get_prop!(get_border_spacing, LayoutBorderSpacingValue, BorderSpacing, as_border_spacing);
2683 impl_get_prop!(get_caption_side, StyleCaptionSideValue, CaptionSide, as_caption_side);
2684 impl_get_prop!(get_empty_cells, StyleEmptyCellsValue, EmptyCells, as_empty_cells);
2685
2686 pub fn calc_width(
2688 &self,
2689 node_data: &NodeData,
2690 node_id: &NodeId,
2691 styled_node_state: &StyledNodeState,
2692 reference_width: f32,
2693 ) -> f32 {
2694 self.get_width(node_data, node_id, styled_node_state)
2695 .and_then(|w| match w.get_property()? {
2696 LayoutWidth::Px(px) => Some(px.to_pixels_internal(
2697 reference_width,
2698 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2699 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2700 )),
2701 _ => Some(0.0), })
2703 .unwrap_or(0.0)
2704 }
2705
2706 pub fn calc_min_width(
2707 &self,
2708 node_data: &NodeData,
2709 node_id: &NodeId,
2710 styled_node_state: &StyledNodeState,
2711 reference_width: f32,
2712 ) -> f32 {
2713 self.get_min_width(node_data, node_id, styled_node_state)
2714 .and_then(|w| {
2715 Some(w.get_property()?.inner.to_pixels_internal(
2716 reference_width,
2717 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2718 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2719 ))
2720 })
2721 .unwrap_or(0.0)
2722 }
2723
2724 pub fn calc_max_width(
2725 &self,
2726 node_data: &NodeData,
2727 node_id: &NodeId,
2728 styled_node_state: &StyledNodeState,
2729 reference_width: f32,
2730 ) -> Option<f32> {
2731 self.get_max_width(node_data, node_id, styled_node_state)
2732 .and_then(|w| {
2733 Some(w.get_property()?.inner.to_pixels_internal(
2734 reference_width,
2735 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2736 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2737 ))
2738 })
2739 }
2740
2741 pub fn calc_height(
2743 &self,
2744 node_data: &NodeData,
2745 node_id: &NodeId,
2746 styled_node_state: &StyledNodeState,
2747 reference_height: f32,
2748 ) -> f32 {
2749 self.get_height(node_data, node_id, styled_node_state)
2750 .and_then(|h| match h.get_property()? {
2751 LayoutHeight::Px(px) => Some(px.to_pixels_internal(
2752 reference_height,
2753 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2754 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2755 )),
2756 _ => Some(0.0), })
2758 .unwrap_or(0.0)
2759 }
2760
2761 pub fn calc_min_height(
2762 &self,
2763 node_data: &NodeData,
2764 node_id: &NodeId,
2765 styled_node_state: &StyledNodeState,
2766 reference_height: f32,
2767 ) -> f32 {
2768 self.get_min_height(node_data, node_id, styled_node_state)
2769 .and_then(|h| {
2770 Some(h.get_property()?.inner.to_pixels_internal(
2771 reference_height,
2772 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2773 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2774 ))
2775 })
2776 .unwrap_or(0.0)
2777 }
2778
2779 pub fn calc_max_height(
2780 &self,
2781 node_data: &NodeData,
2782 node_id: &NodeId,
2783 styled_node_state: &StyledNodeState,
2784 reference_height: f32,
2785 ) -> Option<f32> {
2786 self.get_max_height(node_data, node_id, styled_node_state)
2787 .and_then(|h| {
2788 Some(h.get_property()?.inner.to_pixels_internal(
2789 reference_height,
2790 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2791 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2792 ))
2793 })
2794 }
2795
2796 pub fn calc_left(
2798 &self,
2799 node_data: &NodeData,
2800 node_id: &NodeId,
2801 styled_node_state: &StyledNodeState,
2802 reference_width: f32,
2803 ) -> Option<f32> {
2804 self.get_left(node_data, node_id, styled_node_state)
2805 .and_then(|l| {
2806 Some(l.get_property()?.inner.to_pixels_internal(
2807 reference_width,
2808 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2809 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2810 ))
2811 })
2812 }
2813
2814 pub fn calc_right(
2815 &self,
2816 node_data: &NodeData,
2817 node_id: &NodeId,
2818 styled_node_state: &StyledNodeState,
2819 reference_width: f32,
2820 ) -> Option<f32> {
2821 self.get_right(node_data, node_id, styled_node_state)
2822 .and_then(|r| {
2823 Some(r.get_property()?.inner.to_pixels_internal(
2824 reference_width,
2825 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2826 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2827 ))
2828 })
2829 }
2830
2831 pub fn calc_top(
2832 &self,
2833 node_data: &NodeData,
2834 node_id: &NodeId,
2835 styled_node_state: &StyledNodeState,
2836 reference_height: f32,
2837 ) -> Option<f32> {
2838 self.get_top(node_data, node_id, styled_node_state)
2839 .and_then(|t| {
2840 Some(t.get_property()?.inner.to_pixels_internal(
2841 reference_height,
2842 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2843 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2844 ))
2845 })
2846 }
2847
2848 pub fn calc_bottom(
2849 &self,
2850 node_data: &NodeData,
2851 node_id: &NodeId,
2852 styled_node_state: &StyledNodeState,
2853 reference_height: f32,
2854 ) -> Option<f32> {
2855 self.get_bottom(node_data, node_id, styled_node_state)
2856 .and_then(|b| {
2857 Some(b.get_property()?.inner.to_pixels_internal(
2858 reference_height,
2859 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2860 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2861 ))
2862 })
2863 }
2864
2865 pub fn calc_border_left_width(
2867 &self,
2868 node_data: &NodeData,
2869 node_id: &NodeId,
2870 styled_node_state: &StyledNodeState,
2871 reference_width: f32,
2872 ) -> f32 {
2873 self.get_border_left_width(node_data, node_id, styled_node_state)
2874 .and_then(|b| {
2875 Some(b.get_property()?.inner.to_pixels_internal(
2876 reference_width,
2877 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2878 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2879 ))
2880 })
2881 .unwrap_or(0.0)
2882 }
2883
2884 pub fn calc_border_right_width(
2885 &self,
2886 node_data: &NodeData,
2887 node_id: &NodeId,
2888 styled_node_state: &StyledNodeState,
2889 reference_width: f32,
2890 ) -> f32 {
2891 self.get_border_right_width(node_data, node_id, styled_node_state)
2892 .and_then(|b| {
2893 Some(b.get_property()?.inner.to_pixels_internal(
2894 reference_width,
2895 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2896 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2897 ))
2898 })
2899 .unwrap_or(0.0)
2900 }
2901
2902 pub fn calc_border_top_width(
2903 &self,
2904 node_data: &NodeData,
2905 node_id: &NodeId,
2906 styled_node_state: &StyledNodeState,
2907 reference_height: f32,
2908 ) -> f32 {
2909 self.get_border_top_width(node_data, node_id, styled_node_state)
2910 .and_then(|b| {
2911 Some(b.get_property()?.inner.to_pixels_internal(
2912 reference_height,
2913 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2914 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2915 ))
2916 })
2917 .unwrap_or(0.0)
2918 }
2919
2920 pub fn calc_border_bottom_width(
2921 &self,
2922 node_data: &NodeData,
2923 node_id: &NodeId,
2924 styled_node_state: &StyledNodeState,
2925 reference_height: f32,
2926 ) -> f32 {
2927 self.get_border_bottom_width(node_data, node_id, styled_node_state)
2928 .and_then(|b| {
2929 Some(b.get_property()?.inner.to_pixels_internal(
2930 reference_height,
2931 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2932 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2933 ))
2934 })
2935 .unwrap_or(0.0)
2936 }
2937
2938 pub fn calc_padding_left(
2940 &self,
2941 node_data: &NodeData,
2942 node_id: &NodeId,
2943 styled_node_state: &StyledNodeState,
2944 reference_width: f32,
2945 ) -> f32 {
2946 self.get_padding_left(node_data, node_id, styled_node_state)
2947 .and_then(|p| {
2948 Some(p.get_property()?.inner.to_pixels_internal(
2949 reference_width,
2950 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2951 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2952 ))
2953 })
2954 .unwrap_or(0.0)
2955 }
2956
2957 pub fn calc_padding_right(
2958 &self,
2959 node_data: &NodeData,
2960 node_id: &NodeId,
2961 styled_node_state: &StyledNodeState,
2962 reference_width: f32,
2963 ) -> f32 {
2964 self.get_padding_right(node_data, node_id, styled_node_state)
2965 .and_then(|p| {
2966 Some(p.get_property()?.inner.to_pixels_internal(
2967 reference_width,
2968 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2969 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2970 ))
2971 })
2972 .unwrap_or(0.0)
2973 }
2974
2975 pub fn calc_padding_top(
2976 &self,
2977 node_data: &NodeData,
2978 node_id: &NodeId,
2979 styled_node_state: &StyledNodeState,
2980 reference_height: f32,
2981 ) -> f32 {
2982 self.get_padding_top(node_data, node_id, styled_node_state)
2983 .and_then(|p| {
2984 Some(p.get_property()?.inner.to_pixels_internal(
2985 reference_height,
2986 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2987 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
2988 ))
2989 })
2990 .unwrap_or(0.0)
2991 }
2992
2993 pub fn calc_padding_bottom(
2994 &self,
2995 node_data: &NodeData,
2996 node_id: &NodeId,
2997 styled_node_state: &StyledNodeState,
2998 reference_height: f32,
2999 ) -> f32 {
3000 self.get_padding_bottom(node_data, node_id, styled_node_state)
3001 .and_then(|p| {
3002 Some(p.get_property()?.inner.to_pixels_internal(
3003 reference_height,
3004 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3005 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3006 ))
3007 })
3008 .unwrap_or(0.0)
3009 }
3010
3011 pub fn calc_margin_left(
3013 &self,
3014 node_data: &NodeData,
3015 node_id: &NodeId,
3016 styled_node_state: &StyledNodeState,
3017 reference_width: f32,
3018 ) -> f32 {
3019 self.get_margin_left(node_data, node_id, styled_node_state)
3020 .and_then(|m| {
3021 Some(m.get_property()?.inner.to_pixels_internal(
3022 reference_width,
3023 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3024 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3025 ))
3026 })
3027 .unwrap_or(0.0)
3028 }
3029
3030 pub fn calc_margin_right(
3031 &self,
3032 node_data: &NodeData,
3033 node_id: &NodeId,
3034 styled_node_state: &StyledNodeState,
3035 reference_width: f32,
3036 ) -> f32 {
3037 self.get_margin_right(node_data, node_id, styled_node_state)
3038 .and_then(|m| {
3039 Some(m.get_property()?.inner.to_pixels_internal(
3040 reference_width,
3041 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3042 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3043 ))
3044 })
3045 .unwrap_or(0.0)
3046 }
3047
3048 pub fn calc_margin_top(
3049 &self,
3050 node_data: &NodeData,
3051 node_id: &NodeId,
3052 styled_node_state: &StyledNodeState,
3053 reference_height: f32,
3054 ) -> f32 {
3055 self.get_margin_top(node_data, node_id, styled_node_state)
3056 .and_then(|m| {
3057 Some(m.get_property()?.inner.to_pixels_internal(
3058 reference_height,
3059 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3060 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3061 ))
3062 })
3063 .unwrap_or(0.0)
3064 }
3065
3066 pub fn calc_margin_bottom(
3067 &self,
3068 node_data: &NodeData,
3069 node_id: &NodeId,
3070 styled_node_state: &StyledNodeState,
3071 reference_height: f32,
3072 ) -> f32 {
3073 self.get_margin_bottom(node_data, node_id, styled_node_state)
3074 .and_then(|m| {
3075 Some(m.get_property()?.inner.to_pixels_internal(
3076 reference_height,
3077 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3078 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3079 ))
3080 })
3081 .unwrap_or(0.0)
3082 }
3083
3084 #[allow(clippy::too_many_lines)] fn resolve_property_dependency(
3086 target_property: &CssProperty,
3087 reference_property: &CssProperty,
3088 ) -> Option<CssProperty> {
3089 #[allow(clippy::wildcard_imports)]
3092 use azul_css::{
3093 css::CssPropertyValue,
3094 props::{
3095 basic::{font::StyleFontSize, length::SizeMetric, pixel::PixelValue},
3096 layout::*,
3097 style::{SelectionRadius, StyleLetterSpacing, StyleWordSpacing},
3098 },
3099 };
3100
3101 let get_pixel_value = |prop: &CssProperty| -> Option<PixelValue> {
3103 match prop {
3104 CssProperty::FontSize(val) => val.get_property().map(|v| v.inner),
3105 CssProperty::LetterSpacing(val) => val.get_property().map(|v| v.inner),
3106 CssProperty::WordSpacing(val) => val.get_property().map(|v| v.inner),
3107 CssProperty::PaddingLeft(val) => val.get_property().map(|v| v.inner),
3108 CssProperty::PaddingRight(val) => val.get_property().map(|v| v.inner),
3109 CssProperty::PaddingTop(val) => val.get_property().map(|v| v.inner),
3110 CssProperty::PaddingBottom(val) => val.get_property().map(|v| v.inner),
3111 CssProperty::MarginLeft(val) => val.get_property().map(|v| v.inner),
3112 CssProperty::MarginRight(val) => val.get_property().map(|v| v.inner),
3113 CssProperty::MarginTop(val) => val.get_property().map(|v| v.inner),
3114 CssProperty::MarginBottom(val) => val.get_property().map(|v| v.inner),
3115 CssProperty::MinWidth(val) => val.get_property().map(|v| v.inner),
3116 CssProperty::MinHeight(val) => val.get_property().map(|v| v.inner),
3117 CssProperty::MaxWidth(val) => val.get_property().map(|v| v.inner),
3118 CssProperty::MaxHeight(val) => val.get_property().map(|v| v.inner),
3119 CssProperty::SelectionRadius(val) => val.get_property().map(|v| v.inner),
3120 _ => None,
3121 }
3122 };
3123
3124 let target_pixel_value = get_pixel_value(target_property)?;
3125 let reference_pixel_value = get_pixel_value(reference_property)?;
3126
3127 let reference_px = match reference_pixel_value.metric {
3129 SizeMetric::Px => reference_pixel_value.number.get(),
3130 SizeMetric::Pt => reference_pixel_value.number.get() * PT_TO_PX,
3131 SizeMetric::In => reference_pixel_value.number.get() * IN_TO_PX,
3132 SizeMetric::Cm => reference_pixel_value.number.get() * CM_TO_PX,
3133 SizeMetric::Mm => reference_pixel_value.number.get() * MM_TO_PX,
3134 SizeMetric::Em
3136 | SizeMetric::Rem
3137 | SizeMetric::Percent
3138 | SizeMetric::Vw
3139 | SizeMetric::Vh
3140 | SizeMetric::Vmin
3141 | SizeMetric::Vmax => return None,
3142 };
3143
3144 let resolved_px = match target_pixel_value.metric {
3146 SizeMetric::Px => target_pixel_value.number.get(),
3147 SizeMetric::Pt => target_pixel_value.number.get() * PT_TO_PX,
3148 SizeMetric::In => target_pixel_value.number.get() * IN_TO_PX,
3149 SizeMetric::Cm => target_pixel_value.number.get() * CM_TO_PX,
3150 SizeMetric::Mm => target_pixel_value.number.get() * MM_TO_PX,
3151 SizeMetric::Em | SizeMetric::Rem => target_pixel_value.number.get() * reference_px,
3153 SizeMetric::Percent => target_pixel_value.number.get() / 100.0 * reference_px,
3154 SizeMetric::Vw | SizeMetric::Vh | SizeMetric::Vmin | SizeMetric::Vmax => return None,
3156 };
3157
3158 let resolved_pixel_value = PixelValue::px(resolved_px);
3160
3161 match target_property {
3162 CssProperty::FontSize(_) => Some(CssProperty::FontSize(CssPropertyValue::Exact(
3163 StyleFontSize {
3164 inner: resolved_pixel_value,
3165 },
3166 ))),
3167 CssProperty::LetterSpacing(_) => Some(CssProperty::LetterSpacing(
3168 CssPropertyValue::Exact(StyleLetterSpacing {
3169 inner: resolved_pixel_value,
3170 }),
3171 )),
3172 CssProperty::WordSpacing(_) => Some(CssProperty::WordSpacing(CssPropertyValue::Exact(
3173 StyleWordSpacing {
3174 inner: resolved_pixel_value,
3175 },
3176 ))),
3177 CssProperty::PaddingLeft(_) => Some(CssProperty::PaddingLeft(CssPropertyValue::Exact(
3178 LayoutPaddingLeft {
3179 inner: resolved_pixel_value,
3180 },
3181 ))),
3182 CssProperty::PaddingRight(_) => Some(CssProperty::PaddingRight(
3183 CssPropertyValue::Exact(LayoutPaddingRight {
3184 inner: resolved_pixel_value,
3185 }),
3186 )),
3187 CssProperty::PaddingTop(_) => Some(CssProperty::PaddingTop(CssPropertyValue::Exact(
3188 LayoutPaddingTop {
3189 inner: resolved_pixel_value,
3190 },
3191 ))),
3192 CssProperty::PaddingBottom(_) => Some(CssProperty::PaddingBottom(
3193 CssPropertyValue::Exact(LayoutPaddingBottom {
3194 inner: resolved_pixel_value,
3195 }),
3196 )),
3197 CssProperty::MarginLeft(_) => Some(CssProperty::MarginLeft(CssPropertyValue::Exact(
3198 LayoutMarginLeft {
3199 inner: resolved_pixel_value,
3200 },
3201 ))),
3202 CssProperty::MarginRight(_) => Some(CssProperty::MarginRight(CssPropertyValue::Exact(
3203 LayoutMarginRight {
3204 inner: resolved_pixel_value,
3205 },
3206 ))),
3207 CssProperty::MarginTop(_) => Some(CssProperty::MarginTop(CssPropertyValue::Exact(
3208 LayoutMarginTop {
3209 inner: resolved_pixel_value,
3210 },
3211 ))),
3212 CssProperty::MarginBottom(_) => Some(CssProperty::MarginBottom(
3213 CssPropertyValue::Exact(LayoutMarginBottom {
3214 inner: resolved_pixel_value,
3215 }),
3216 )),
3217 CssProperty::MinWidth(_) => Some(CssProperty::MinWidth(CssPropertyValue::Exact(
3218 LayoutMinWidth {
3219 inner: resolved_pixel_value,
3220 },
3221 ))),
3222 CssProperty::MinHeight(_) => Some(CssProperty::MinHeight(CssPropertyValue::Exact(
3223 LayoutMinHeight {
3224 inner: resolved_pixel_value,
3225 },
3226 ))),
3227 CssProperty::MaxWidth(_) => Some(CssProperty::MaxWidth(CssPropertyValue::Exact(
3228 LayoutMaxWidth {
3229 inner: resolved_pixel_value,
3230 },
3231 ))),
3232 CssProperty::MaxHeight(_) => Some(CssProperty::MaxHeight(CssPropertyValue::Exact(
3233 LayoutMaxHeight {
3234 inner: resolved_pixel_value,
3235 },
3236 ))),
3237 CssProperty::SelectionRadius(_) => Some(CssProperty::SelectionRadius(
3238 CssPropertyValue::Exact(SelectionRadius {
3239 inner: resolved_pixel_value,
3240 }),
3241 )),
3242 _ => None,
3243 }
3244 }
3245
3246 pub fn apply_ua_css(&mut self, node_data: &[NodeData]) {
3256 use azul_css::props::property::CssPropertyType;
3257 use azul_css::dynamic_selector::PseudoStateType;
3258
3259 let node_count = node_data.len();
3260 if node_count == 0 {
3261 return;
3262 }
3263
3264 let mut prop_set: Vec<[u128; 2]> = vec![[0u128; 2]; node_count];
3267
3268 for (node_idx, props) in self.css_props.iter_node_slices() {
3270 for p in props {
3271 if p.state == PseudoStateType::Normal {
3272 let d = p.prop_type as u16 as usize;
3273 if d < 128 {
3274 prop_set[node_idx][0] |= 1u128 << d;
3275 } else {
3276 prop_set[node_idx][1] |= 1u128 << (d - 128);
3277 }
3278 }
3279 }
3280 }
3281
3282 for (node_idx, props) in self.cascaded_props.iter_node_slices() {
3284 for p in props {
3285 if p.state == PseudoStateType::Normal {
3286 let d = p.prop_type as u16 as usize;
3287 if d < 128 {
3288 prop_set[node_idx][0] |= 1u128 << d;
3289 } else {
3290 prop_set[node_idx][1] |= 1u128 << (d - 128);
3291 }
3292 }
3293 }
3294 }
3295
3296 for (node_idx, node) in node_data.iter().enumerate() {
3298 for (prop, conds) in node.style.iter_inline_properties() {
3299 let is_normal = conds.as_slice().is_empty();
3300 if is_normal {
3301 let d = prop.get_type() as u16 as usize;
3302 if d < 128 {
3303 prop_set[node_idx][0] |= 1u128 << d;
3304 } else {
3305 prop_set[node_idx][1] |= 1u128 << (d - 128);
3306 }
3307 }
3308 }
3309 }
3310
3311 let property_types = [
3313 CssPropertyType::Display,
3314 CssPropertyType::Width,
3315 CssPropertyType::Height,
3316 CssPropertyType::FontSize,
3317 CssPropertyType::FontWeight,
3318 CssPropertyType::FontFamily,
3319 CssPropertyType::MarginTop,
3320 CssPropertyType::MarginBottom,
3321 CssPropertyType::MarginLeft,
3322 CssPropertyType::MarginRight,
3323 CssPropertyType::PaddingTop,
3324 CssPropertyType::PaddingBottom,
3325 CssPropertyType::PaddingLeft,
3326 CssPropertyType::PaddingRight,
3327 CssPropertyType::BorderTopStyle,
3328 CssPropertyType::BorderTopWidth,
3329 CssPropertyType::BorderTopColor,
3330 CssPropertyType::BreakInside,
3331 CssPropertyType::BreakAfter,
3332 CssPropertyType::ListStyleType,
3333 CssPropertyType::CounterReset,
3334 CssPropertyType::TextDecoration,
3335 CssPropertyType::TextAlign,
3336 CssPropertyType::VerticalAlign,
3337 CssPropertyType::Cursor,
3338 ];
3339
3340 for (node_index, node) in node_data.iter().enumerate() {
3342 let node_type = &node.node_type;
3343
3344 for prop_type in &property_types {
3345 let d = *prop_type as u16 as usize;
3347 let has_prop = if d < 128 {
3348 (prop_set[node_index][0] & (1u128 << d)) != 0
3349 } else {
3350 (prop_set[node_index][1] & (1u128 << (d - 128))) != 0
3351 };
3352
3353 if has_prop {
3354 continue;
3355 }
3356
3357 if let Some(ua_prop) = crate::ua_css::get_ua_property(node_type, *prop_type) {
3359 self.cascaded_props.push_to(node_index, StatefulCssProperty {
3360 state: PseudoStateType::Normal,
3361 prop_type: *prop_type,
3362 property: ua_prop.clone(),
3363 });
3364
3365 if d < 128 {
3367 prop_set[node_index][0] |= 1u128 << d;
3368 } else {
3369 prop_set[node_index][1] |= 1u128 << (d - 128);
3370 }
3371 }
3372 }
3373 }
3374 }
3375
3376 pub fn sort_cascaded_props(&mut self) {
3379 self.cascaded_props.sort_each_and_flatten(|p| (p.state, p.prop_type));
3380 }
3381
3382 pub fn compute_inherited_values(
3388 &mut self,
3389 node_hierarchy: &[NodeHierarchyItem],
3390 node_data: &[NodeData],
3391 ) -> Vec<NodeId> {
3392 if self.computed_values.len() < node_hierarchy.len() {
3393 self.computed_values.resize(node_hierarchy.len(), Vec::new());
3394 }
3395 node_hierarchy
3396 .iter()
3397 .enumerate()
3398 .filter_map(|(node_index, hierarchy_item)| {
3399 let node_id = NodeId::new(node_index);
3400 let parent_id = hierarchy_item.parent_id();
3401 let parent_computed: Option<Vec<(CssPropertyType, CssPropertyWithOrigin)>> =
3402 parent_id.and_then(|pid| self.computed_values.get(pid.index()).cloned());
3403
3404 let mut ctx = InheritanceContext {
3405 node_id,
3406 parent_id,
3407 computed_values: Vec::new(),
3408 };
3409
3410 if let Some(ref parent_values) = parent_computed {
3412 Self::inherit_from_parent(&mut ctx, parent_values);
3413 }
3414
3415 self.apply_cascade_properties(
3417 &mut ctx,
3418 node_id,
3419 parent_computed.as_ref(),
3420 node_data,
3421 node_index,
3422 );
3423
3424 let changed = self.store_if_changed(&ctx);
3426 changed.then_some(node_id)
3427 })
3428 .collect()
3429 }
3430
3431 fn inherit_from_parent(
3433 ctx: &mut InheritanceContext,
3434 parent_values: &[(CssPropertyType, CssPropertyWithOrigin)],
3435 ) {
3436 for (prop_type, prop_with_origin) in
3437 parent_values.iter().filter(|(pt, _)| pt.is_inheritable())
3438 {
3439 let entry = (*prop_type, CssPropertyWithOrigin {
3440 property: prop_with_origin.property.clone(),
3441 origin: CssPropertyOrigin::Inherited,
3442 });
3443 match ctx.computed_values.binary_search_by_key(prop_type, |(k, _)| *k) {
3445 Ok(idx) => ctx.computed_values[idx] = entry,
3446 Err(idx) => ctx.computed_values.insert(idx, entry),
3447 }
3448 }
3449 }
3450
3451 fn apply_cascade_properties(
3453 &self,
3454 ctx: &mut InheritanceContext,
3455 node_id: NodeId,
3456 parent_computed: Option<&Vec<(CssPropertyType, CssPropertyWithOrigin)>>,
3457 node_data: &[NodeData],
3458 node_index: usize,
3459 ) {
3460 {
3462 let cascaded_slice = self.cascaded_props.get_slice(node_id.index());
3463 for p in cascaded_slice {
3464 if p.state == azul_css::dynamic_selector::PseudoStateType::Normal
3465 && Self::should_apply_cascaded(&ctx.computed_values, p.prop_type, &p.property) {
3466 Self::process_property(ctx, &p.property, parent_computed);
3467 }
3468 }
3469 }
3470
3471 {
3473 let css_slice = self.css_props.get_slice(node_id.index());
3474 for p in css_slice {
3475 if p.state == azul_css::dynamic_selector::PseudoStateType::Normal {
3476 Self::process_property(ctx, &p.property, parent_computed);
3477 }
3478 }
3479 }
3480
3481 for (prop, conds) in node_data[node_index].style.iter_inline_properties() {
3483 if conds.as_slice().is_empty() {
3485 Self::process_property(ctx, prop, parent_computed);
3486 }
3487 }
3488
3489 if let Some(user_props) = self.user_overridden_properties.get(node_id.index()) {
3491 for (_, prop) in user_props {
3492 Self::process_property(ctx, prop, parent_computed);
3493 }
3494 }
3495 }
3496
3497 fn should_apply_cascaded(
3499 computed: &[(CssPropertyType, CssPropertyWithOrigin)],
3500 prop_type: CssPropertyType,
3501 prop: &CssProperty,
3502 ) -> bool {
3503 if prop_type == CssPropertyType::FontSize {
3505 if let Ok(idx) = computed.binary_search_by_key(&prop_type, |(k, _)| *k) {
3506 if computed[idx].1.origin == CssPropertyOrigin::Inherited
3507 && Self::has_relative_font_size_unit(prop)
3508 {
3509 return false;
3510 }
3511 }
3512 }
3513
3514 computed.binary_search_by_key(&prop_type, |(k, _)| *k).map_or(true, |idx| computed[idx].1.origin == CssPropertyOrigin::Inherited)
3515 }
3516
3517 fn process_property(
3519 ctx: &mut InheritanceContext,
3520 prop: &CssProperty,
3521 parent_computed: Option<&Vec<(CssPropertyType, CssPropertyWithOrigin)>>,
3522 ) {
3523 let prop_type = prop.get_type();
3524
3525 let resolved = if prop_type == CssPropertyType::FontSize {
3526 Self::resolve_font_size_property(prop, parent_computed)
3527 } else {
3528 Self::resolve_other_property(prop, &ctx.computed_values)
3529 };
3530
3531 let entry = (prop_type, CssPropertyWithOrigin {
3532 property: resolved,
3533 origin: CssPropertyOrigin::Own,
3534 });
3535 match ctx.computed_values.binary_search_by_key(&prop_type, |(k, _)| *k) {
3536 Ok(idx) => ctx.computed_values[idx] = entry,
3537 Err(idx) => ctx.computed_values.insert(idx, entry),
3538 }
3539 }
3540
3541 fn resolve_font_size_property(
3543 prop: &CssProperty,
3544 parent_computed: Option<&Vec<(CssPropertyType, CssPropertyWithOrigin)>>,
3545 ) -> CssProperty {
3546 let parent_font_size = parent_computed
3547 .and_then(|p| {
3548 p.binary_search_by_key(&CssPropertyType::FontSize, |(k, _)| *k)
3549 .ok()
3550 .map(|idx| &p[idx].1)
3551 });
3552
3553 parent_font_size.map_or_else(|| Self::resolve_font_size_to_pixels(
3554 prop,
3555 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3556 ), |pfs| Self::resolve_property_dependency(prop, &pfs.property).unwrap_or_else(
3557 || {
3558 Self::resolve_font_size_to_pixels(
3559 prop,
3560 azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3561 )
3562 },
3563 ))
3564 }
3565
3566 fn resolve_other_property(
3568 prop: &CssProperty,
3569 computed: &[(CssPropertyType, CssPropertyWithOrigin)],
3570 ) -> CssProperty {
3571 computed
3572 .binary_search_by_key(&CssPropertyType::FontSize, |(k, _)| *k)
3573 .ok()
3574 .and_then(|idx| Self::resolve_property_dependency(prop, &computed[idx].1.property))
3575 .unwrap_or_else(|| prop.clone())
3576 }
3577
3578 fn resolve_font_size_to_pixels(prop: &CssProperty, reference_px: f32) -> CssProperty {
3580 use azul_css::{
3581 css::CssPropertyValue,
3582 props::basic::{font::StyleFontSize, length::SizeMetric, pixel::PixelValue},
3583 };
3584
3585 let CssProperty::FontSize(css_val) = prop else {
3586 return prop.clone();
3587 };
3588
3589 let Some(font_size) = css_val.get_property() else {
3590 return prop.clone();
3591 };
3592
3593 let resolved_px = match font_size.inner.metric {
3594 SizeMetric::Px => font_size.inner.number.get(),
3595 SizeMetric::Pt => font_size.inner.number.get() * PT_TO_PX,
3596 SizeMetric::In => font_size.inner.number.get() * IN_TO_PX,
3597 SizeMetric::Cm => font_size.inner.number.get() * CM_TO_PX,
3598 SizeMetric::Mm => font_size.inner.number.get() * MM_TO_PX,
3599 SizeMetric::Em => font_size.inner.number.get() * reference_px,
3600 SizeMetric::Rem => {
3601 font_size.inner.number.get() * azul_css::props::basic::pixel::DEFAULT_FONT_SIZE
3602 }
3603 SizeMetric::Percent => font_size.inner.number.get() / 100.0 * reference_px,
3604 SizeMetric::Vw | SizeMetric::Vh | SizeMetric::Vmin | SizeMetric::Vmax => {
3605 return prop.clone();
3606 }
3607 };
3608
3609 CssProperty::FontSize(CssPropertyValue::Exact(StyleFontSize {
3610 inner: PixelValue::px(resolved_px),
3611 }))
3612 }
3613
3614 fn has_relative_font_size_unit(prop: &CssProperty) -> bool {
3616 use azul_css::props::basic::length::SizeMetric;
3617
3618 let CssProperty::FontSize(css_val) = prop else {
3619 return false;
3620 };
3621
3622 css_val
3623 .get_property()
3624 .is_some_and(|fs| {
3625 matches!(
3626 fs.inner.metric,
3627 SizeMetric::Em | SizeMetric::Rem | SizeMetric::Percent
3628 )
3629 })
3630 }
3631
3632 fn store_if_changed(&mut self, ctx: &InheritanceContext) -> bool {
3634 let values_changed = self
3635 .computed_values
3636 .get(ctx.node_id.index()) != Some(&ctx.computed_values);
3637
3638 self.computed_values[ctx.node_id.index()].clone_from(&ctx.computed_values);
3639
3640 values_changed
3641 }
3642}
3643
3644struct InheritanceContext {
3646 node_id: NodeId,
3647 parent_id: Option<NodeId>,
3648 computed_values: Vec<(CssPropertyType, CssPropertyWithOrigin)>,
3649}
3650
3651impl CssPropertyCache {
3652
3653 pub(crate) fn invalidate_resolved_cache(&mut self) {
3655 self.compact_cache = None;
3656 }
3657}
3658
3659#[cfg(test)]
3660#[allow(clippy::float_cmp, clippy::too_many_lines)]
3661mod autotest_generated {
3662 use azul_css::{
3663 css::CssPropertyValue,
3664 dynamic_selector::{
3665 CssPropertyWithConditions, DynamicSelector, DynamicSelectorContext, PseudoStateType,
3666 },
3667 props::{
3668 basic::{length::SizeMetric, pixel::PixelValue},
3669 layout::{
3670 LayoutFlexBasis, LayoutInsetBottom, LayoutLeft, LayoutMarginTop, LayoutMaxWidth,
3671 LayoutMinWidth, LayoutOverflow, LayoutPaddingLeft, LayoutRight, LayoutTop,
3672 },
3673 style::LayoutBorderLeftWidth,
3674 },
3675 };
3676
3677 use super::*;
3678
3679 fn close(a: f32, b: f32) -> bool {
3686 (a - b).abs() < 0.01
3687 }
3688
3689 fn n0() -> NodeId {
3690 NodeId::new(0)
3691 }
3692
3693 fn normal() -> StyledNodeState {
3694 StyledNodeState::default()
3695 }
3696
3697 fn div_with(props: Vec<CssProperty>) -> NodeData {
3699 let mut nd = NodeData::create_div();
3700 for property in props {
3701 nd.add_css_property(CssPropertyWithConditions {
3702 property,
3703 apply_if: Vec::new().into(),
3704 });
3705 }
3706 nd
3707 }
3708
3709 fn div_with_pseudo(props: Vec<CssProperty>, state: PseudoStateType) -> NodeData {
3711 let mut nd = NodeData::create_div();
3712 for property in props {
3713 nd.add_css_property(CssPropertyWithConditions {
3714 property,
3715 apply_if: vec![DynamicSelector::PseudoState(state)].into(),
3716 });
3717 }
3718 nd
3719 }
3720
3721 fn width_px(v: f32) -> CssProperty {
3722 CssProperty::Width(CssPropertyValue::Exact(LayoutWidth::Px(PixelValue::px(v))))
3723 }
3724
3725 fn width_pct(v: f32) -> CssProperty {
3726 CssProperty::Width(CssPropertyValue::Exact(LayoutWidth::Px(PixelValue::percent(
3727 v,
3728 ))))
3729 }
3730
3731 fn font_size(pv: PixelValue) -> CssProperty {
3732 CssProperty::FontSize(CssPropertyValue::Exact(StyleFontSize { inner: pv }))
3733 }
3734
3735 fn font_size_parts(p: &CssProperty) -> Option<(SizeMetric, f32)> {
3737 match p {
3738 CssProperty::FontSize(v) => v
3739 .get_property()
3740 .map(|fs| (fs.inner.metric, fs.inner.number.get())),
3741 _ => None,
3742 }
3743 }
3744
3745 fn stateful(state: PseudoStateType, property: CssProperty) -> StatefulCssProperty {
3746 StatefulCssProperty {
3747 state,
3748 prop_type: property.get_type(),
3749 property,
3750 }
3751 }
3752
3753 #[test]
3758 fn flatvecvec_new_zero_is_empty() {
3759 let f = FlatVecVec::<i32>::new(0);
3760 assert_eq!(f.len(), 0);
3761 assert!(f.is_empty());
3762 assert!(f.is_flattened());
3765 assert!(f.get_slice(0).is_empty());
3766 }
3767
3768 #[test]
3769 fn flatvecvec_new_invariants_hold() {
3770 let f = FlatVecVec::<i32>::new(3);
3771 assert_eq!(f.len(), 3);
3772 assert!(!f.is_empty());
3773 assert!(!f.is_flattened(), "fresh multi-slot vec is in build phase");
3774 assert_eq!(f.build_get(0), Some(&Vec::new()));
3775 assert_eq!(f.build_get(2), Some(&Vec::new()));
3776 assert_eq!(f.build_get(3), None, "one past the end");
3777 assert_eq!(f.build_get(usize::MAX), None);
3778 assert!(f.get_slice(0).is_empty());
3779 }
3780
3781 #[test]
3782 fn flatvecvec_default_is_neutral() {
3783 let f = FlatVecVec::<i32>::default();
3784 assert_eq!(f.len(), 0);
3785 assert!(f.is_empty());
3786 assert_eq!(f.build_get(0), None);
3787 assert!(f.get_slice(0).is_empty());
3788 }
3789
3790 #[test]
3791 fn flatvecvec_get_slice_out_of_bounds_is_empty_in_both_phases() {
3792 let mut f = FlatVecVec::<i32>::new(2);
3793 f.push_to(0, 7);
3794 assert_eq!(f.get_slice(0), &[7]);
3796 assert!(f.get_slice(2).is_empty());
3797 assert!(f.get_slice(usize::MAX).is_empty());
3798
3799 f.flatten();
3800 assert_eq!(f.get_slice(0), &[7]);
3802 assert!(f.get_slice(2).is_empty());
3803 assert!(f.get_slice(usize::MAX).is_empty());
3804 }
3805
3806 #[test]
3807 #[should_panic(expected = "index out of bounds")]
3808 fn flatvecvec_push_to_out_of_bounds_panics() {
3809 let mut f = FlatVecVec::<i32>::new(1);
3811 f.push_to(1, 0);
3812 }
3813
3814 #[test]
3815 #[should_panic(expected = "index out of bounds")]
3816 fn flatvecvec_push_to_after_flatten_panics() {
3817 let mut f = FlatVecVec::<i32>::new(1);
3819 f.flatten();
3820 f.push_to(0, 0);
3821 }
3822
3823 #[test]
3824 #[should_panic(expected = "index out of bounds")]
3825 fn flatvecvec_build_mut_out_of_bounds_panics() {
3826 let mut f = FlatVecVec::<i32>::new(1);
3827 let _ = f.build_mut(usize::MAX);
3828 }
3829
3830 #[test]
3831 fn flatvecvec_build_iter_mut_visits_every_slot() {
3832 let mut f = FlatVecVec::<i32>::new(3);
3833 f.push_to(0, 1);
3834 f.push_to(2, 2);
3835 let mut visited = 0;
3836 for v in f.build_iter_mut() {
3837 visited += 1;
3838 v.clear();
3839 }
3840 assert_eq!(visited, 3);
3841 assert!(f.get_slice(0).is_empty());
3842 assert!(f.get_slice(2).is_empty());
3843 }
3844
3845 #[test]
3846 fn flatvecvec_build_get_returns_none_once_flattened() {
3847 let mut f = FlatVecVec::<i32>::new(1);
3848 f.push_to(0, 5);
3849 f.flatten();
3850 assert_eq!(f.build_get(0), None);
3852 assert_eq!(f.get_slice(0), &[5]);
3853 }
3854
3855 #[test]
3860 fn flatvecvec_heap_bytes_zero_and_empty() {
3861 let f = FlatVecVec::<i32>::default();
3862 assert_eq!(f.heap_bytes(0), 0, "empty vec, zero element size");
3863 assert_eq!(f.heap_bytes(usize::MAX), 0);
3866 assert_eq!(f.heap_bytes(size_of::<i32>()), 0);
3867 }
3868
3869 #[test]
3870 fn flatvecvec_heap_bytes_counts_build_and_flat_storage() {
3871 let mut f = FlatVecVec::<i32>::new(4);
3872 assert!(f.heap_bytes(0) >= 4 * size_of::<Vec<i32>>());
3875
3876 f.push_to(0, 1);
3877 f.push_to(0, 2);
3878 let build_bytes = f.heap_bytes(size_of::<i32>());
3879 assert!(build_bytes > 0);
3880
3881 f.flatten();
3882 let flat_bytes = f.heap_bytes(size_of::<i32>());
3884 assert!(flat_bytes >= 2 * size_of::<i32>() + 4 * size_of::<(u32, u32)>());
3885 }
3886
3887 #[test]
3892 fn flatvecvec_sort_each_and_flatten_keeps_last_of_equal_keys() {
3893 let mut f = FlatVecVec::<(i32, i32)>::new(1);
3895 f.push_to(0, (1, 10));
3896 f.push_to(0, (1, 20)); f.push_to(0, (0, 30));
3898 f.sort_each_and_flatten(|p| p.0);
3899
3900 assert!(f.is_flattened());
3901 assert_eq!(f.get_slice(0), &[(0, 30), (1, 20)]);
3902 }
3903
3904 #[test]
3905 fn flatvecvec_sort_each_and_flatten_on_empty_slots() {
3906 let mut f = FlatVecVec::<i32>::new(3);
3907 f.push_to(1, 42);
3908 f.sort_each_and_flatten(|v| *v);
3909 assert_eq!(f.len(), 3);
3910 assert!(f.get_slice(0).is_empty());
3911 assert_eq!(f.get_slice(1), &[42]);
3912 assert!(f.get_slice(2).is_empty());
3913 }
3914
3915 #[test]
3916 fn flatvecvec_sort_each_and_flatten_on_zero_nodes_does_not_panic() {
3917 let mut f = FlatVecVec::<i32>::new(0);
3918 f.sort_each_and_flatten(|v| *v);
3919 assert_eq!(f.len(), 0);
3920 assert!(f.get_slice(0).is_empty());
3921 }
3922
3923 #[test]
3924 fn flatvecvec_flatten_does_not_deduplicate() {
3925 let mut f = FlatVecVec::<i32>::new(2);
3926 f.push_to(0, 5);
3927 f.push_to(0, 5);
3928 f.push_to(1, 9);
3929 f.flatten();
3930 assert!(f.is_flattened());
3931 assert_eq!(f.get_slice(0), &[5, 5], "flatten() must not dedup");
3932 assert_eq!(f.get_slice(1), &[9]);
3933 }
3934
3935 #[test]
3940 fn flatvecvec_retain_before_flatten_is_a_noop() {
3941 let mut f = FlatVecVec::<i32>::new(1);
3944 f.push_to(0, 1);
3945 f.push_to(0, 2);
3946 f.retain(|_| false);
3947 assert_eq!(f.get_slice(0), &[1, 2], "build-phase data left untouched");
3948 }
3949
3950 #[test]
3951 fn flatvecvec_retain_preserves_per_node_order() {
3952 let mut f = FlatVecVec::<i32>::new(2);
3953 for v in [1, 2, 3, 4] {
3954 f.push_to(0, v);
3955 }
3956 f.push_to(1, 5);
3957 f.flatten();
3958
3959 f.retain(|v| v % 2 == 0);
3960 assert_eq!(f.get_slice(0), &[2, 4]);
3961 assert!(f.get_slice(1).is_empty());
3962 assert_eq!(f.len(), 2, "node slots survive an empty retain");
3963 }
3964
3965 #[test]
3966 fn flatvecvec_retain_dropping_everything_leaves_empty_slices() {
3967 let mut f = FlatVecVec::<i32>::new(2);
3968 f.push_to(0, 1);
3969 f.push_to(1, 2);
3970 f.flatten();
3971 f.retain(|_| false);
3972 assert_eq!(f.len(), 2);
3973 assert!(f.get_slice(0).is_empty());
3974 assert!(f.get_slice(1).is_empty());
3975 }
3976
3977 #[test]
3978 fn flatvecvec_retain_with_node_index_sees_owning_node() {
3979 let mut f = FlatVecVec::<i32>::new(3);
3980 f.push_to(0, 10);
3981 f.push_to(1, 11);
3982 f.push_to(2, 12);
3983 f.flatten();
3984
3985 f.retain_with_node_index(|idx, _| idx == 1);
3986 assert!(f.get_slice(0).is_empty());
3987 assert_eq!(f.get_slice(1), &[11]);
3988 assert!(f.get_slice(2).is_empty());
3989 }
3990
3991 #[test]
3992 fn flatvecvec_retain_with_node_index_before_flatten_is_a_noop() {
3993 let mut f = FlatVecVec::<i32>::new(1);
3994 f.push_to(0, 1);
3995 f.retain_with_node_index(|_, _| false);
3996 assert_eq!(f.get_slice(0), &[1]);
3997 }
3998
3999 #[test]
4004 fn flatvecvec_iter_node_slices_covers_all_nodes_in_both_phases() {
4005 let mut f = FlatVecVec::<i32>::new(3);
4006 f.push_to(1, 7);
4007
4008 let build: Vec<(usize, Vec<i32>)> = f
4009 .iter_node_slices()
4010 .map(|(i, s)| (i, s.to_vec()))
4011 .collect();
4012 assert_eq!(build, vec![(0, vec![]), (1, vec![7]), (2, vec![])]);
4013
4014 f.flatten();
4015 let flat: Vec<(usize, Vec<i32>)> = f
4016 .iter_node_slices()
4017 .map(|(i, s)| (i, s.to_vec()))
4018 .collect();
4019 assert_eq!(flat, build, "iteration is phase-independent");
4020 }
4021
4022 #[test]
4023 fn flatvecvec_iter_node_slices_on_empty_yields_nothing() {
4024 let f = FlatVecVec::<i32>::new(0);
4025 assert_eq!(f.iter_node_slices().count(), 0);
4026 }
4027
4028 #[test]
4029 fn flatvecvec_extend_from_both_in_build_phase() {
4030 let mut a = FlatVecVec::<i32>::new(1);
4031 a.push_to(0, 1);
4032 let mut b = FlatVecVec::<i32>::new(2);
4033 b.push_to(0, 2);
4034 b.push_to(1, 3);
4035
4036 a.extend_from(&mut b);
4037 assert_eq!(a.len(), 3);
4038 assert_eq!(a.get_slice(0), &[1]);
4039 assert_eq!(a.get_slice(1), &[2]);
4040 assert_eq!(a.get_slice(2), &[3]);
4041 assert_eq!(b.len(), 0, "other is drained");
4042 }
4043
4044 #[test]
4045 fn flatvecvec_extend_from_both_flattened_rebases_offsets() {
4046 let mut a = FlatVecVec::<i32>::new(2);
4047 a.push_to(0, 1);
4048 a.push_to(1, 2);
4049 a.flatten();
4050
4051 let mut b = FlatVecVec::<i32>::new(2);
4052 b.push_to(0, 3);
4053 b.push_to(1, 4);
4054 b.flatten();
4055
4056 a.extend_from(&mut b);
4057 assert_eq!(a.len(), 4);
4058 assert_eq!(a.get_slice(0), &[1]);
4059 assert_eq!(a.get_slice(1), &[2]);
4060 assert_eq!(a.get_slice(2), &[3], "offsets rebased onto a's flat data");
4061 assert_eq!(a.get_slice(3), &[4]);
4062 }
4063
4064 #[test]
4065 fn flatvecvec_extend_from_across_phases_discards_self_flat_data() {
4066 let mut a = FlatVecVec::<i32>::new(1);
4070 a.push_to(0, 1);
4071 a.flatten();
4072
4073 let mut b = FlatVecVec::<i32>::new(1);
4074 b.push_to(0, 2);
4075
4076 a.extend_from(&mut b); assert_eq!(a.len(), 1);
4078 assert_eq!(
4079 a.get_slice(0),
4080 &[2],
4081 "a's own flattened item (1) is silently lost"
4082 );
4083 }
4084
4085 #[test]
4086 fn flatvecvec_eq_within_the_same_phase() {
4087 let mut a = FlatVecVec::<i32>::new(1);
4088 a.push_to(0, 1);
4089 let mut b = FlatVecVec::<i32>::new(1);
4090 b.push_to(0, 1);
4091 assert_eq!(a, b);
4092
4093 b.push_to(0, 2);
4094 assert_ne!(a, b);
4095
4096 a.flatten();
4097 let mut c = FlatVecVec::<i32>::new(1);
4100 c.push_to(0, 1);
4101 c.flatten();
4102 assert_eq!(a, c);
4103 }
4104
4105 #[test]
4110 fn breakdown_total_bytes_sums_subfields_and_excludes_node_count() {
4111 let b = CssPropertyCacheBreakdown {
4112 node_count: 999_999,
4113 cascaded_props_bytes: 1,
4114 css_props_bytes: 2,
4115 computed_values_bytes: 4,
4116 user_overridden_bytes: 8,
4117 global_css_props_bytes: 16,
4118 compact_cache_bytes: 32,
4119 resolved_font_sizes_bytes: 64,
4120 };
4121 assert_eq!(b.total_bytes(), 127, "node_count is not a byte count");
4122 }
4123
4124 #[test]
4125 fn breakdown_total_bytes_default_is_zero_and_max_single_field_does_not_overflow() {
4126 assert_eq!(CssPropertyCacheBreakdown::default().total_bytes(), 0);
4127
4128 let b = CssPropertyCacheBreakdown {
4129 cascaded_props_bytes: usize::MAX,
4130 ..Default::default()
4131 };
4132 assert_eq!(b.total_bytes(), usize::MAX);
4133 }
4134
4135 #[test]
4140 fn cache_empty_zero_is_neutral() {
4141 let c = CssPropertyCache::empty(0);
4142 assert_eq!(c.node_count, 0);
4143 assert!(c.css_props.is_empty());
4144 assert!(c.cascaded_props.is_empty());
4145 assert!(c.computed_values.is_empty());
4146 assert!(c.user_overridden_properties.is_empty());
4147 assert!(c.global_css_props.is_empty());
4148 assert!(c.compact_cache.is_none());
4149
4150 let b = c.memory_breakdown();
4151 assert_eq!(b.node_count, 0);
4152 assert_eq!(b.total_bytes(), 0, "a zero-node cache retains no heap");
4153 }
4154
4155 #[test]
4156 fn cache_empty_invariants_hold() {
4157 let c = CssPropertyCache::empty(7);
4158 assert_eq!(c.node_count, 7);
4159 assert_eq!(c.css_props.len(), 7);
4160 assert_eq!(c.cascaded_props.len(), 7);
4161 assert!(!c.css_props.is_flattened(), "starts in build phase");
4162 assert!(c.compact_cache.is_none());
4163
4164 let b = c.memory_breakdown();
4165 assert_eq!(b.node_count, 7);
4166 assert!(b.total_bytes() > 0);
4167 assert_eq!(b.compact_cache_bytes, 0);
4168 assert_eq!(b.resolved_font_sizes_bytes, 0);
4169 }
4170
4171 #[test]
4172 fn cache_invalidate_resolved_font_sizes_clears_the_once_lock() {
4173 let mut c = CssPropertyCache::empty(1);
4174 assert!(c.resolved_font_sizes_px.set(vec![16.0]).is_ok());
4175 assert!(c.resolved_font_sizes_px.get().is_some());
4176
4177 c.invalidate_resolved_font_sizes();
4178 assert!(
4179 c.resolved_font_sizes_px.get().is_none(),
4180 "next read must recompute"
4181 );
4182 assert!(c.resolved_font_sizes_px.set(vec![12.0]).is_ok());
4184 }
4185
4186 #[test]
4187 fn cache_append_sums_nodes_and_invalidates_derived_caches() {
4188 let mut a = CssPropertyCache::empty(2);
4189 let mut b = CssPropertyCache::empty(3);
4190 assert!(a.resolved_font_sizes_px.set(vec![16.0, 16.0]).is_ok());
4191
4192 a.append(&mut b);
4193
4194 assert_eq!(a.node_count, 5);
4195 assert_eq!(a.css_props.len(), 5);
4196 assert_eq!(a.cascaded_props.len(), 5);
4197 assert!(
4198 a.resolved_font_sizes_px.get().is_none(),
4199 "node indices shifted"
4200 );
4201 assert!(a.compact_cache.is_none());
4202 }
4203
4204 #[test]
4205 fn cache_append_of_empty_cache_is_a_noop_on_node_count() {
4206 let mut a = CssPropertyCache::empty(2);
4207 let mut b = CssPropertyCache::empty(0);
4208 a.append(&mut b);
4209 assert_eq!(a.node_count, 2);
4210 assert_eq!(a.css_props.len(), 2);
4211 }
4212
4213 #[test]
4214 fn cache_invalidate_resolved_cache_drops_compact_cache() {
4215 let mut c = CssPropertyCache::empty(1);
4216 c.invalidate_resolved_cache();
4217 assert!(c.compact_cache.is_none());
4218 }
4219
4220 #[test]
4221 fn cache_ptr_new_and_downcast_roundtrip() {
4222 let mut p = CssPropertyCachePtr::new(CssPropertyCache::empty(4));
4223 assert!(p.run_destructor);
4224 assert_eq!(p.downcast_mut().node_count, 4);
4225
4226 p.downcast_mut().node_count = 9;
4227 assert_eq!(p.downcast_mut().node_count, 9, "downcast_mut aliases the box");
4228 }
4229
4230 #[test]
4235 fn overflow_predicates_default_to_visible_for_a_bare_div() {
4236 let c = CssPropertyCache::empty(1);
4237 let nd = NodeData::create_div();
4238 assert!(c.is_horizontal_overflow_visible(&nd, &n0(), &normal()));
4239 assert!(c.is_vertical_overflow_visible(&nd, &n0(), &normal()));
4240 assert!(!c.is_horizontal_overflow_hidden(&nd, &n0(), &normal()));
4241 assert!(!c.is_vertical_overflow_hidden(&nd, &n0(), &normal()));
4242 }
4243
4244 #[test]
4245 fn overflow_predicates_are_per_axis() {
4246 let c = CssPropertyCache::empty(1);
4247 let nd = div_with(vec![CssProperty::OverflowX(CssPropertyValue::Exact(
4248 LayoutOverflow::Hidden,
4249 ))]);
4250 assert!(c.is_horizontal_overflow_hidden(&nd, &n0(), &normal()));
4251 assert!(!c.is_horizontal_overflow_visible(&nd, &n0(), &normal()));
4252 assert!(!c.is_vertical_overflow_hidden(&nd, &n0(), &normal()));
4254 assert!(c.is_vertical_overflow_visible(&nd, &n0(), &normal()));
4255 }
4256
4257 #[test]
4258 fn overflow_predicates_do_not_panic_on_an_out_of_range_node_id() {
4259 let c = CssPropertyCache::empty(0);
4260 let nd = NodeData::create_div();
4261 let far = NodeId::new(999_999);
4262 assert!(c.is_horizontal_overflow_visible(&nd, &far, &normal()));
4263 assert!(!c.is_vertical_overflow_hidden(&nd, &far, &normal()));
4264 }
4265
4266 #[test]
4267 fn has_border_false_without_and_true_with_a_border_width() {
4268 let c = CssPropertyCache::empty(1);
4269 assert!(!c.has_border(&NodeData::create_div(), &n0(), &normal()));
4270
4271 let bordered = div_with(vec![CssProperty::BorderLeftWidth(CssPropertyValue::Exact(
4272 LayoutBorderLeftWidth {
4273 inner: PixelValue::px(2.0),
4274 },
4275 ))]);
4276 assert!(c.has_border(&bordered, &n0(), &normal()));
4277 }
4278
4279 #[test]
4280 fn has_box_shadow_false_for_a_bare_div() {
4281 let c = CssPropertyCache::empty(1);
4282 assert!(!c.has_box_shadow(&NodeData::create_div(), &n0(), &normal()));
4283 assert!(!c.has_box_shadow(&NodeData::create_div(), &NodeId::new(500), &normal()));
4285 }
4286
4287 #[test]
4292 fn or_default_getters_fall_back_to_the_css_defaults() {
4293 let c = CssPropertyCache::empty(1);
4294 let nd = NodeData::create_div();
4295
4296 assert_eq!(
4297 c.get_font_size_or_default(&nd, &n0(), &normal()),
4298 azul_css::defaults::DEFAULT_FONT_SIZE
4299 );
4300 assert_eq!(
4301 c.get_text_color_or_default(&nd, &n0(), &normal()),
4302 azul_css::defaults::DEFAULT_TEXT_COLOR
4303 );
4304
4305 let fams = c.get_font_id_or_default(&nd, &n0(), &normal());
4306 assert_eq!(fams.as_ref().len(), 1);
4307 match &fams.as_ref()[0] {
4308 StyleFontFamily::System(s) => {
4309 assert_eq!(s.as_str(), azul_css::defaults::DEFAULT_FONT_ID);
4310 }
4311 other => panic!("expected the default System font family, got {other:?}"),
4312 }
4313 }
4314
4315 #[test]
4316 fn get_font_size_or_default_prefers_the_inline_value() {
4317 let c = CssPropertyCache::empty(1);
4318 let nd = div_with(vec![font_size(PixelValue::px(42.0))]);
4319 let fs = c.get_font_size_or_default(&nd, &n0(), &normal());
4320 assert!(close(fs.inner.number.get(), 42.0));
4321 assert_eq!(fs.inner.metric, SizeMetric::Px);
4322 }
4323
4324 #[test]
4325 fn or_default_getters_survive_an_out_of_range_node_id() {
4326 let c = CssPropertyCache::empty(0);
4327 let nd = NodeData::create_div();
4328 let far = NodeId::new(usize::MAX / 2);
4329 assert_eq!(
4330 c.get_font_size_or_default(&nd, &far, &normal()),
4331 azul_css::defaults::DEFAULT_FONT_SIZE
4332 );
4333 assert_eq!(c.get_font_id_or_default(&nd, &far, &normal()).as_ref().len(), 1);
4334 }
4335
4336 #[test]
4341 fn calc_width_is_zero_when_unset() {
4342 let c = CssPropertyCache::empty(1);
4343 let nd = NodeData::create_div();
4344 assert_eq!(c.calc_width(&nd, &n0(), &normal(), 800.0), 0.0);
4345 assert_eq!(c.calc_width(&nd, &n0(), &normal(), 0.0), 0.0);
4346 assert_eq!(c.calc_height(&nd, &n0(), &normal(), f32::NAN), 0.0);
4347 }
4348
4349 #[test]
4350 fn calc_width_resolves_px_and_percent() {
4351 let c = CssPropertyCache::empty(1);
4352
4353 let px = div_with(vec![width_px(100.0)]);
4354 assert!(close(c.calc_width(&px, &n0(), &normal(), 800.0), 100.0));
4355 assert!(close(c.calc_width(&px, &n0(), &normal(), 0.0), 100.0));
4357
4358 let pct = div_with(vec![width_pct(50.0)]);
4359 assert!(close(c.calc_width(&pct, &n0(), &normal(), 800.0), 400.0));
4360 assert!(close(c.calc_width(&pct, &n0(), &normal(), 0.0), 0.0));
4361 }
4362
4363 #[test]
4364 fn calc_width_with_a_negative_reference_is_negative_not_clamped() {
4365 let c = CssPropertyCache::empty(1);
4366 let pct = div_with(vec![width_pct(50.0)]);
4367 assert!(close(c.calc_width(&pct, &n0(), &normal(), -800.0), -400.0));
4368 }
4369
4370 #[test]
4371 fn calc_width_with_nan_and_infinite_references_is_defined() {
4372 let c = CssPropertyCache::empty(1);
4373 let pct = div_with(vec![width_pct(50.0)]);
4374
4375 assert!(c.calc_width(&pct, &n0(), &normal(), f32::NAN).is_nan());
4376 assert_eq!(
4377 c.calc_width(&pct, &n0(), &normal(), f32::INFINITY),
4378 f32::INFINITY
4379 );
4380 assert_eq!(
4381 c.calc_width(&pct, &n0(), &normal(), f32::NEG_INFINITY),
4382 f32::NEG_INFINITY
4383 );
4384 }
4385
4386 #[test]
4387 fn calc_width_saturates_non_finite_pixel_values_at_construction() {
4388 let c = CssPropertyCache::empty(1);
4389
4390 let nan = div_with(vec![width_px(f32::NAN)]);
4394 assert_eq!(c.calc_width(&nan, &n0(), &normal(), 800.0), 0.0);
4395
4396 let inf = div_with(vec![width_px(f32::INFINITY)]);
4397 let got = c.calc_width(&inf, &n0(), &normal(), 800.0);
4398 assert!(got.is_finite() && got > 0.0, "saturated, got {got}");
4399
4400 let neg_inf = div_with(vec![width_px(f32::NEG_INFINITY)]);
4401 let got = c.calc_width(&neg_inf, &n0(), &normal(), 800.0);
4402 assert!(got.is_finite() && got < 0.0, "saturated, got {got}");
4403
4404 let huge = div_with(vec![width_px(f32::MAX)]);
4405 assert!(c.calc_width(&huge, &n0(), &normal(), 800.0).is_finite());
4406 }
4407
4408 #[test]
4409 fn calc_width_of_auto_and_intrinsic_keywords_is_zero() {
4410 let c = CssPropertyCache::empty(1);
4411
4412 let auto = div_with(vec![CssProperty::Width(CssPropertyValue::Auto)]);
4413 assert_eq!(c.calc_width(&auto, &n0(), &normal(), 800.0), 0.0);
4414
4415 let min_content = div_with(vec![CssProperty::Width(CssPropertyValue::Exact(
4417 LayoutWidth::MinContent,
4418 ))]);
4419 assert_eq!(c.calc_width(&min_content, &n0(), &normal(), 800.0), 0.0);
4420 }
4421
4422 #[test]
4423 fn calc_height_mirrors_calc_width() {
4424 let c = CssPropertyCache::empty(1);
4425 let nd = div_with(vec![CssProperty::Height(CssPropertyValue::Exact(
4426 LayoutHeight::Px(PixelValue::percent(25.0)),
4427 ))]);
4428 assert!(close(c.calc_height(&nd, &n0(), &normal(), 400.0), 100.0));
4429 assert!(c.calc_height(&nd, &n0(), &normal(), f32::NAN).is_nan());
4430 }
4431
4432 #[test]
4433 fn calc_min_width_defaults_to_zero_and_max_width_defaults_to_none() {
4434 let c = CssPropertyCache::empty(1);
4435 let nd = NodeData::create_div();
4436
4437 assert_eq!(c.calc_min_width(&nd, &n0(), &normal(), 800.0), 0.0);
4438 assert_eq!(c.calc_min_height(&nd, &n0(), &normal(), 600.0), 0.0);
4439 assert_eq!(c.calc_max_width(&nd, &n0(), &normal(), 800.0), None);
4440 assert_eq!(c.calc_max_height(&nd, &n0(), &normal(), 600.0), None);
4441 }
4442
4443 #[test]
4444 fn calc_min_max_width_resolve_percentages_and_propagate_nan() {
4445 let c = CssPropertyCache::empty(1);
4446 let nd = div_with(vec![
4447 CssProperty::MinWidth(CssPropertyValue::Exact(LayoutMinWidth {
4448 inner: PixelValue::percent(10.0),
4449 })),
4450 CssProperty::MaxWidth(CssPropertyValue::Exact(LayoutMaxWidth {
4451 inner: PixelValue::percent(90.0),
4452 })),
4453 ]);
4454
4455 assert!(close(c.calc_min_width(&nd, &n0(), &normal(), 1000.0), 100.0));
4456 assert!(close(
4457 c.calc_max_width(&nd, &n0(), &normal(), 1000.0).unwrap(),
4458 900.0
4459 ));
4460 assert!(c.calc_min_width(&nd, &n0(), &normal(), f32::NAN).is_nan());
4461 assert!(c
4462 .calc_max_width(&nd, &n0(), &normal(), f32::NAN)
4463 .unwrap()
4464 .is_nan());
4465 }
4466
4467 #[test]
4468 fn calc_inset_getters_are_none_when_unset_and_some_when_set() {
4469 let c = CssPropertyCache::empty(1);
4470 let bare = NodeData::create_div();
4471 assert_eq!(c.calc_left(&bare, &n0(), &normal(), 800.0), None);
4472 assert_eq!(c.calc_right(&bare, &n0(), &normal(), 800.0), None);
4473 assert_eq!(c.calc_top(&bare, &n0(), &normal(), 600.0), None);
4474 assert_eq!(c.calc_bottom(&bare, &n0(), &normal(), 600.0), None);
4475
4476 let inset = div_with(vec![
4477 CssProperty::Left(CssPropertyValue::Exact(LayoutLeft {
4478 inner: PixelValue::px(5.0),
4479 })),
4480 CssProperty::Right(CssPropertyValue::Exact(LayoutRight {
4481 inner: PixelValue::percent(10.0),
4482 })),
4483 CssProperty::Top(CssPropertyValue::Exact(LayoutTop {
4484 inner: PixelValue::px(-7.0),
4485 })),
4486 CssProperty::Bottom(CssPropertyValue::Exact(LayoutInsetBottom {
4487 inner: PixelValue::px(0.0),
4488 })),
4489 ]);
4490 assert!(close(c.calc_left(&inset, &n0(), &normal(), 800.0).unwrap(), 5.0));
4491 assert!(close(
4492 c.calc_right(&inset, &n0(), &normal(), 800.0).unwrap(),
4493 80.0
4494 ));
4495 assert!(close(
4496 c.calc_top(&inset, &n0(), &normal(), 600.0).unwrap(),
4497 -7.0
4498 ));
4499 assert_eq!(c.calc_bottom(&inset, &n0(), &normal(), 600.0), Some(0.0));
4500 }
4501
4502 #[test]
4503 fn calc_padding_margin_border_default_to_zero() {
4504 let c = CssPropertyCache::empty(1);
4505 let nd = NodeData::create_div();
4506 assert_eq!(c.calc_padding_left(&nd, &n0(), &normal(), 800.0), 0.0);
4507 assert_eq!(c.calc_padding_right(&nd, &n0(), &normal(), 800.0), 0.0);
4508 assert_eq!(c.calc_padding_top(&nd, &n0(), &normal(), 600.0), 0.0);
4509 assert_eq!(c.calc_padding_bottom(&nd, &n0(), &normal(), 600.0), 0.0);
4510 assert_eq!(c.calc_margin_left(&nd, &n0(), &normal(), 800.0), 0.0);
4511 assert_eq!(c.calc_margin_right(&nd, &n0(), &normal(), 800.0), 0.0);
4512 assert_eq!(c.calc_margin_top(&nd, &n0(), &normal(), 600.0), 0.0);
4513 assert_eq!(c.calc_margin_bottom(&nd, &n0(), &normal(), 600.0), 0.0);
4514 assert_eq!(c.calc_border_left_width(&nd, &n0(), &normal(), 800.0), 0.0);
4515 assert_eq!(c.calc_border_right_width(&nd, &n0(), &normal(), 800.0), 0.0);
4516 assert_eq!(c.calc_border_top_width(&nd, &n0(), &normal(), 600.0), 0.0);
4517 assert_eq!(c.calc_border_bottom_width(&nd, &n0(), &normal(), 600.0), 0.0);
4518 }
4519
4520 #[test]
4521 fn calc_padding_em_uses_the_default_font_size_not_the_reference() {
4522 let c = CssPropertyCache::empty(1);
4525 let nd = div_with(vec![CssProperty::PaddingLeft(CssPropertyValue::Exact(
4526 LayoutPaddingLeft {
4527 inner: PixelValue::em(2.0),
4528 },
4529 ))]);
4530 assert!(close(c.calc_padding_left(&nd, &n0(), &normal(), 800.0), 32.0));
4531 assert!(close(c.calc_padding_left(&nd, &n0(), &normal(), 0.0), 32.0));
4532 assert!(close(
4533 c.calc_padding_left(&nd, &n0(), &normal(), f32::NAN),
4534 32.0
4535 ));
4536 }
4537
4538 #[test]
4539 fn calc_margin_and_border_resolve_px_and_percent() {
4540 let c = CssPropertyCache::empty(1);
4541 let nd = div_with(vec![
4542 CssProperty::MarginTop(CssPropertyValue::Exact(LayoutMarginTop {
4543 inner: PixelValue::percent(50.0),
4544 })),
4545 CssProperty::BorderLeftWidth(CssPropertyValue::Exact(LayoutBorderLeftWidth {
4546 inner: PixelValue::px(3.0),
4547 })),
4548 ]);
4549 assert!(close(c.calc_margin_top(&nd, &n0(), &normal(), 200.0), 100.0));
4550 assert!(close(
4551 c.calc_border_left_width(&nd, &n0(), &normal(), 800.0),
4552 3.0
4553 ));
4554 assert!(c.calc_margin_top(&nd, &n0(), &normal(), f32::NAN).is_nan());
4555 }
4556
4557 #[test]
4558 fn calc_getters_do_not_panic_on_an_out_of_range_node_id() {
4559 let c = CssPropertyCache::empty(0);
4560 let nd = NodeData::create_div();
4561 let far = NodeId::new(usize::MAX / 2);
4562 assert_eq!(c.calc_width(&nd, &far, &normal(), 800.0), 0.0);
4563 assert_eq!(c.calc_max_height(&nd, &far, &normal(), 600.0), None);
4564 assert_eq!(c.calc_padding_top(&nd, &far, &normal(), f32::INFINITY), 0.0);
4565 }
4566
4567 #[test]
4572 fn slow_path_only_needed_for_non_px_pixel_values() {
4573 assert!(!property_needs_slow_path_after_compact(&width_px(10.0)));
4575 assert!(property_needs_slow_path_after_compact(&width_pct(50.0)));
4577
4578 assert!(!property_needs_slow_path_after_compact(&CssProperty::Height(
4579 CssPropertyValue::Exact(LayoutHeight::Px(PixelValue::px(1.0)))
4580 )));
4581 assert!(property_needs_slow_path_after_compact(&CssProperty::Height(
4582 CssPropertyValue::Exact(LayoutHeight::Px(PixelValue::em(1.0)))
4583 )));
4584 }
4585
4586 #[test]
4587 fn slow_path_covers_the_plain_pixelvalue_wrappers() {
4588 assert!(property_needs_slow_path_after_compact(&font_size(
4589 PixelValue::rem(2.0)
4590 )));
4591 assert!(!property_needs_slow_path_after_compact(&font_size(
4592 PixelValue::px(16.0)
4593 )));
4594
4595 assert!(property_needs_slow_path_after_compact(
4596 &CssProperty::MinWidth(CssPropertyValue::Exact(LayoutMinWidth {
4597 inner: PixelValue::percent(10.0),
4598 }))
4599 ));
4600 assert!(!property_needs_slow_path_after_compact(
4601 &CssProperty::PaddingLeft(CssPropertyValue::Exact(LayoutPaddingLeft {
4602 inner: PixelValue::px(4.0),
4603 }))
4604 ));
4605 }
4606
4607 #[test]
4608 fn slow_path_handles_flex_basis_and_non_pixel_properties() {
4609 assert!(property_needs_slow_path_after_compact(
4610 &CssProperty::FlexBasis(CssPropertyValue::Exact(LayoutFlexBasis::Exact(
4611 PixelValue::percent(50.0)
4612 )))
4613 ));
4614 assert!(!property_needs_slow_path_after_compact(
4615 &CssProperty::FlexBasis(CssPropertyValue::Exact(LayoutFlexBasis::Auto))
4616 ));
4617
4618 assert!(!property_needs_slow_path_after_compact(&CssProperty::Width(
4620 CssPropertyValue::Auto
4621 )));
4622 assert!(!property_needs_slow_path_after_compact(
4623 &CssProperty::const_none(CssPropertyType::Display)
4624 ));
4625 assert!(!property_needs_slow_path_after_compact(
4626 &CssProperty::const_none(CssPropertyType::BackgroundContent)
4627 ));
4628 }
4629
4630 #[test]
4635 fn clone_inheritable_property_round_trips_heap_and_pod_variants() {
4636 let font_family = CssProperty::FontFamily(CssPropertyValue::Exact(
4639 vec![StyleFontFamily::System(AzString::from_const_str("serif"))].into(),
4640 ));
4641 assert_eq!(clone_inheritable_property(&font_family), font_family);
4642
4643 for p in [
4644 CssProperty::const_none(CssPropertyType::Cursor),
4645 CssProperty::const_none(CssPropertyType::TextColor),
4646 CssProperty::const_none(CssPropertyType::BackgroundContent),
4647 CssProperty::const_none(CssPropertyType::Transform),
4648 CssProperty::const_none(CssPropertyType::Content),
4649 width_px(3.0),
4650 font_size(PixelValue::em(1.5)),
4651 ] {
4652 assert_eq!(clone_inheritable_property(&p), p, "clone must be identity");
4653 assert_eq!(clone_inheritable_property(&p).get_type(), p.get_type());
4654 }
4655 }
4656
4657 fn sorted_stateful_fixture() -> Vec<StatefulCssProperty> {
4662 let mut v = vec![
4663 stateful(PseudoStateType::Normal, width_px(1.0)),
4664 stateful(
4665 PseudoStateType::Normal,
4666 CssProperty::const_none(CssPropertyType::Display),
4667 ),
4668 stateful(PseudoStateType::Hover, width_px(2.0)),
4669 ];
4670 v.sort_by_key(|p| (p.state, p.prop_type));
4672 v
4673 }
4674
4675 #[test]
4676 fn find_in_stateful_on_an_empty_slice_is_none() {
4677 assert!(CssPropertyCache::find_in_stateful(
4678 &[],
4679 PseudoStateType::Normal,
4680 &CssPropertyType::Width
4681 )
4682 .is_none());
4683 }
4684
4685 #[test]
4686 fn find_in_stateful_is_keyed_on_both_state_and_prop_type() {
4687 let v = sorted_stateful_fixture();
4688
4689 let normal_width =
4690 CssPropertyCache::find_in_stateful(&v, PseudoStateType::Normal, &CssPropertyType::Width)
4691 .expect("normal width present");
4692 assert_eq!(normal_width.get_type(), CssPropertyType::Width);
4693
4694 let hover_width =
4695 CssPropertyCache::find_in_stateful(&v, PseudoStateType::Hover, &CssPropertyType::Width)
4696 .expect("hover width present");
4697 assert_ne!(normal_width, hover_width);
4699
4700 assert!(CssPropertyCache::find_in_stateful(
4702 &v,
4703 PseudoStateType::Focus,
4704 &CssPropertyType::Width
4705 )
4706 .is_none());
4707 assert!(CssPropertyCache::find_in_stateful(
4709 &v,
4710 PseudoStateType::Hover,
4711 &CssPropertyType::Display
4712 )
4713 .is_none());
4714 }
4715
4716 #[test]
4717 fn has_state_props_true_false_and_edges() {
4718 let v = sorted_stateful_fixture();
4719 assert!(CssPropertyCache::has_state_props(&v, PseudoStateType::Normal));
4720 assert!(CssPropertyCache::has_state_props(&v, PseudoStateType::Hover));
4721 assert!(!CssPropertyCache::has_state_props(&v, PseudoStateType::Focus));
4722 assert!(!CssPropertyCache::has_state_props(
4724 &[],
4725 PseudoStateType::Normal
4726 ));
4727 }
4728
4729 #[test]
4730 fn prop_types_for_state_filters_by_state() {
4731 let v = sorted_stateful_fixture();
4732
4733 let mut normal: Vec<CssPropertyType> =
4734 CssPropertyCache::prop_types_for_state(&v, PseudoStateType::Normal)
4735 .copied()
4736 .collect();
4737 normal.sort_unstable();
4738 assert_eq!(normal.len(), 2);
4739 assert!(normal.contains(&CssPropertyType::Width));
4740 assert!(normal.contains(&CssPropertyType::Display));
4741
4742 let hover: Vec<CssPropertyType> =
4743 CssPropertyCache::prop_types_for_state(&v, PseudoStateType::Hover)
4744 .copied()
4745 .collect();
4746 assert_eq!(hover, vec![CssPropertyType::Width]);
4747
4748 assert_eq!(
4749 CssPropertyCache::prop_types_for_state(&v, PseudoStateType::Active).count(),
4750 0
4751 );
4752 assert_eq!(
4753 CssPropertyCache::prop_types_for_state(&[], PseudoStateType::Normal).count(),
4754 0
4755 );
4756 }
4757
4758 #[test]
4763 fn resolve_font_size_to_pixels_converts_absolute_units() {
4764 let px = CssPropertyCache::resolve_font_size_to_pixels(&font_size(PixelValue::px(20.0)), 10.0);
4765 let (metric, n) = font_size_parts(&px).unwrap();
4766 assert_eq!(metric, SizeMetric::Px);
4767 assert!(close(n, 20.0));
4768
4769 let pt = CssPropertyCache::resolve_font_size_to_pixels(&font_size(PixelValue::pt(12.0)), 10.0);
4770 assert!(close(font_size_parts(&pt).unwrap().1, 12.0 * PT_TO_PX));
4771 }
4772
4773 #[test]
4774 fn resolve_font_size_to_pixels_em_scales_by_reference_but_rem_does_not() {
4775 let em =
4776 CssPropertyCache::resolve_font_size_to_pixels(&font_size(PixelValue::em(2.0)), 10.0);
4777 assert!(close(font_size_parts(&em).unwrap().1, 20.0));
4778
4779 let rem =
4781 CssPropertyCache::resolve_font_size_to_pixels(&font_size(PixelValue::rem(2.0)), 10.0);
4782 assert!(close(font_size_parts(&rem).unwrap().1, 32.0));
4783
4784 let pct = CssPropertyCache::resolve_font_size_to_pixels(
4785 &font_size(PixelValue::percent(50.0)),
4786 10.0,
4787 );
4788 assert!(close(font_size_parts(&pct).unwrap().1, 5.0));
4789 }
4790
4791 #[test]
4792 fn resolve_font_size_to_pixels_with_nan_and_infinite_references() {
4793 let nan =
4795 CssPropertyCache::resolve_font_size_to_pixels(&font_size(PixelValue::em(2.0)), f32::NAN);
4796 let (metric, n) = font_size_parts(&nan).unwrap();
4797 assert_eq!(metric, SizeMetric::Px);
4798 assert_eq!(n, 0.0, "NaN must not escape into the cascade");
4799
4800 let inf = CssPropertyCache::resolve_font_size_to_pixels(
4801 &font_size(PixelValue::em(2.0)),
4802 f32::INFINITY,
4803 );
4804 let n = font_size_parts(&inf).unwrap().1;
4805 assert!(n.is_finite() && n > 0.0, "saturated, got {n}");
4806
4807 let zero =
4808 CssPropertyCache::resolve_font_size_to_pixels(&font_size(PixelValue::em(2.0)), 0.0);
4809 assert_eq!(font_size_parts(&zero).unwrap().1, 0.0);
4810
4811 let neg =
4812 CssPropertyCache::resolve_font_size_to_pixels(&font_size(PixelValue::em(2.0)), -10.0);
4813 assert!(close(font_size_parts(&neg).unwrap().1, -20.0));
4814 }
4815
4816 #[test]
4817 fn resolve_font_size_to_pixels_passes_through_unresolvable_inputs() {
4818 let vw = font_size(PixelValue::from_metric(SizeMetric::Vw, 10.0));
4820 assert_eq!(CssPropertyCache::resolve_font_size_to_pixels(&vw, 16.0), vw);
4821
4822 let w = width_px(10.0);
4824 assert_eq!(CssPropertyCache::resolve_font_size_to_pixels(&w, 16.0), w);
4825
4826 let inherit = CssProperty::FontSize(CssPropertyValue::Inherit);
4828 assert_eq!(
4829 CssPropertyCache::resolve_font_size_to_pixels(&inherit, 16.0),
4830 inherit
4831 );
4832 }
4833
4834 #[test]
4835 fn has_relative_font_size_unit_true_false_and_edges() {
4836 assert!(CssPropertyCache::has_relative_font_size_unit(&font_size(
4837 PixelValue::em(1.0)
4838 )));
4839 assert!(CssPropertyCache::has_relative_font_size_unit(&font_size(
4840 PixelValue::rem(1.0)
4841 )));
4842 assert!(CssPropertyCache::has_relative_font_size_unit(&font_size(
4843 PixelValue::percent(100.0)
4844 )));
4845
4846 assert!(!CssPropertyCache::has_relative_font_size_unit(&font_size(
4847 PixelValue::px(16.0)
4848 )));
4849 assert!(!CssPropertyCache::has_relative_font_size_unit(&font_size(
4850 PixelValue::pt(12.0)
4851 )));
4852 assert!(!CssPropertyCache::has_relative_font_size_unit(
4854 &CssProperty::FontSize(CssPropertyValue::Auto)
4855 ));
4856 assert!(!CssPropertyCache::has_relative_font_size_unit(&width_px(1.0)));
4857 }
4858
4859 #[test]
4864 fn resolve_property_dependency_scales_relative_targets_by_an_absolute_reference() {
4865 let reference = font_size(PixelValue::px(10.0));
4866
4867 let em = CssPropertyCache::resolve_property_dependency(
4868 &font_size(PixelValue::em(2.0)),
4869 &reference,
4870 )
4871 .expect("em resolves against an absolute reference");
4872 assert!(close(font_size_parts(&em).unwrap().1, 20.0));
4873
4874 let pct = CssPropertyCache::resolve_property_dependency(
4875 &font_size(PixelValue::percent(50.0)),
4876 &reference,
4877 )
4878 .expect("percent resolves");
4879 assert!(close(font_size_parts(&pct).unwrap().1, 5.0));
4880
4881 let pt_ref = font_size(PixelValue::pt(10.0));
4883 let em2 =
4884 CssPropertyCache::resolve_property_dependency(&font_size(PixelValue::em(2.0)), &pt_ref)
4885 .expect("pt reference is absolute");
4886 assert!(close(
4887 font_size_parts(&em2).unwrap().1,
4888 2.0 * 10.0 * PT_TO_PX
4889 ));
4890 }
4891
4892 #[test]
4893 fn resolve_property_dependency_rewrites_the_target_variant_in_place() {
4894 let reference = font_size(PixelValue::px(10.0));
4895 let padding = CssProperty::PaddingLeft(CssPropertyValue::Exact(LayoutPaddingLeft {
4896 inner: PixelValue::em(3.0),
4897 }));
4898 let out = CssPropertyCache::resolve_property_dependency(&padding, &reference)
4899 .expect("padding is a supported target");
4900 match out {
4901 CssProperty::PaddingLeft(v) => {
4902 let inner = v.get_property().unwrap().inner;
4903 assert_eq!(inner.metric, SizeMetric::Px);
4904 assert!(close(inner.number.get(), 30.0));
4905 }
4906 other => panic!("variant must be preserved, got {other:?}"),
4907 }
4908 }
4909
4910 #[test]
4911 fn resolve_property_dependency_returns_none_for_unresolvable_inputs() {
4912 let abs = font_size(PixelValue::px(10.0));
4913
4914 assert!(CssPropertyCache::resolve_property_dependency(
4916 &font_size(PixelValue::em(2.0)),
4917 &font_size(PixelValue::em(2.0))
4918 )
4919 .is_none());
4920 assert!(CssPropertyCache::resolve_property_dependency(
4922 &font_size(PixelValue::from_metric(SizeMetric::Vh, 5.0)),
4923 &abs
4924 )
4925 .is_none());
4926 assert!(CssPropertyCache::resolve_property_dependency(&width_px(5.0), &abs).is_none());
4928 assert!(CssPropertyCache::resolve_property_dependency(
4930 &font_size(PixelValue::em(2.0)),
4931 &width_px(5.0)
4932 )
4933 .is_none());
4934 assert!(CssPropertyCache::resolve_property_dependency(
4936 &CssProperty::FontSize(CssPropertyValue::Inherit),
4937 &abs
4938 )
4939 .is_none());
4940 }
4941
4942 #[test]
4947 fn should_apply_cascaded_respects_origin_and_relative_font_sizes() {
4948 let own = |p: CssProperty| {
4949 vec![(
4950 p.get_type(),
4951 CssPropertyWithOrigin {
4952 property: p,
4953 origin: CssPropertyOrigin::Own,
4954 },
4955 )]
4956 };
4957 let inherited = |p: CssProperty| {
4958 vec![(
4959 p.get_type(),
4960 CssPropertyWithOrigin {
4961 property: p,
4962 origin: CssPropertyOrigin::Inherited,
4963 },
4964 )]
4965 };
4966
4967 assert!(CssPropertyCache::should_apply_cascaded(
4969 &[],
4970 CssPropertyType::Width,
4971 &width_px(1.0)
4972 ));
4973
4974 assert!(!CssPropertyCache::should_apply_cascaded(
4976 &own(width_px(2.0)),
4977 CssPropertyType::Width,
4978 &width_px(1.0)
4979 ));
4980
4981 assert!(CssPropertyCache::should_apply_cascaded(
4983 &inherited(width_px(2.0)),
4984 CssPropertyType::Width,
4985 &width_px(1.0)
4986 ));
4987
4988 let inherited_fs = inherited(font_size(PixelValue::px(20.0)));
4991 assert!(!CssPropertyCache::should_apply_cascaded(
4992 &inherited_fs,
4993 CssPropertyType::FontSize,
4994 &font_size(PixelValue::em(2.0))
4995 ));
4996 assert!(CssPropertyCache::should_apply_cascaded(
4997 &inherited_fs,
4998 CssPropertyType::FontSize,
4999 &font_size(PixelValue::px(12.0))
5000 ));
5001 }
5002
5003 #[test]
5008 fn get_property_finds_an_inline_normal_property() {
5009 let c = CssPropertyCache::empty(1);
5010 let nd = div_with(vec![width_px(100.0)]);
5011 let got = c
5012 .get_property(&nd, &n0(), &normal(), &CssPropertyType::Width)
5013 .expect("inline width");
5014 assert_eq!(*got, width_px(100.0));
5015 }
5016
5017 #[test]
5018 fn get_property_ignores_pseudo_state_props_unless_the_state_is_active() {
5019 let c = CssPropertyCache::empty(1);
5020 let nd = div_with_pseudo(vec![width_px(100.0)], PseudoStateType::Hover);
5021
5022 assert!(
5023 c.get_property(&nd, &n0(), &normal(), &CssPropertyType::Width)
5024 .is_none(),
5025 ":hover width must not leak into the Normal state"
5026 );
5027
5028 let hovered = StyledNodeState {
5029 hover: true,
5030 ..StyledNodeState::default()
5031 };
5032 assert_eq!(
5033 c.get_property(&nd, &n0(), &hovered, &CssPropertyType::Width),
5034 Some(&width_px(100.0))
5035 );
5036 }
5037
5038 #[test]
5039 fn get_property_user_override_beats_inline_and_stylesheet() {
5040 let mut c = CssPropertyCache::empty(1);
5041 c.user_overridden_properties
5042 .push(vec![(CssPropertyType::Width, width_px(1.0))]);
5043 c.css_props
5044 .push_to(0, stateful(PseudoStateType::Normal, width_px(2.0)));
5045 c.css_props.sort_each_and_flatten(|p| (p.state, p.prop_type));
5046
5047 let nd = div_with(vec![width_px(3.0)]);
5048 assert_eq!(
5049 c.get_property(&nd, &n0(), &normal(), &CssPropertyType::Width),
5050 Some(&width_px(1.0)),
5051 "user override is the top cascade layer"
5052 );
5053 }
5054
5055 #[test]
5056 fn get_property_falls_back_through_stylesheet_global_cascaded_then_ua() {
5057 let nd = NodeData::create_div();
5058
5059 let mut c = CssPropertyCache::empty(1);
5061 c.css_props
5062 .push_to(0, stateful(PseudoStateType::Normal, width_px(2.0)));
5063 c.css_props.sort_each_and_flatten(|p| (p.state, p.prop_type));
5064 assert_eq!(
5065 c.get_property(&nd, &n0(), &normal(), &CssPropertyType::Width),
5066 Some(&width_px(2.0))
5067 );
5068
5069 let mut c = CssPropertyCache::empty(1);
5071 c.global_css_props.push(width_px(4.0));
5072 assert_eq!(
5073 c.get_property(&nd, &n0(), &normal(), &CssPropertyType::Width),
5074 Some(&width_px(4.0))
5075 );
5076
5077 let mut c = CssPropertyCache::empty(1);
5079 c.cascaded_props
5080 .push_to(0, stateful(PseudoStateType::Normal, width_px(5.0)));
5081 c.cascaded_props
5082 .sort_each_and_flatten(|p| (p.state, p.prop_type));
5083 assert_eq!(
5084 c.get_property(&nd, &n0(), &normal(), &CssPropertyType::Width),
5085 Some(&width_px(5.0))
5086 );
5087
5088 let c = CssPropertyCache::empty(1);
5090 assert!(c
5091 .get_property(&nd, &n0(), &normal(), &CssPropertyType::Width)
5092 .is_none());
5093 assert!(c
5094 .get_property(&nd, &n0(), &normal(), &CssPropertyType::Display)
5095 .is_some());
5096 }
5097
5098 #[test]
5099 fn get_property_on_an_out_of_range_node_id_falls_through_to_ua_css() {
5100 let c = CssPropertyCache::empty(0);
5101 let nd = NodeData::create_div();
5102 let far = NodeId::new(usize::MAX / 2);
5103
5104 assert!(c
5105 .get_property(&nd, &far, &normal(), &CssPropertyType::Width)
5106 .is_none());
5107 assert!(
5108 c.get_property(&nd, &far, &normal(), &CssPropertyType::Display)
5109 .is_some(),
5110 "UA CSS is node-type-keyed, not index-keyed"
5111 );
5112 }
5113
5114 #[test]
5115 fn get_property_with_context_matches_pseudo_state_conditions() {
5116 let c = CssPropertyCache::empty(1);
5117 let nd = div_with_pseudo(vec![width_px(100.0)], PseudoStateType::Hover);
5118
5119 let plain = DynamicSelectorContext::default();
5120 assert!(c
5121 .get_property_with_context(&nd, &n0(), &plain, &CssPropertyType::Width)
5122 .is_none());
5123
5124 let mut hovered = DynamicSelectorContext::default();
5125 hovered.pseudo_state.hover = true;
5126 assert_eq!(
5127 c.get_property_with_context(&nd, &n0(), &hovered, &CssPropertyType::Width),
5128 Some(&width_px(100.0))
5129 );
5130 }
5131
5132 #[test]
5133 fn check_properties_changed_only_fires_when_a_condition_flips() {
5134 let plain = DynamicSelectorContext::default();
5135 let mut hovered = DynamicSelectorContext::default();
5136 hovered.pseudo_state.hover = true;
5137
5138 let unconditional = div_with(vec![width_px(1.0)]);
5140 assert!(!CssPropertyCache::check_properties_changed(
5141 &unconditional,
5142 &plain,
5143 &hovered
5144 ));
5145
5146 let conditional = div_with_pseudo(vec![width_px(1.0)], PseudoStateType::Hover);
5147 assert!(CssPropertyCache::check_properties_changed(
5148 &conditional,
5149 &plain,
5150 &hovered
5151 ));
5152 assert!(
5153 !CssPropertyCache::check_properties_changed(&conditional, &plain, &plain),
5154 "identical contexts can never differ"
5155 );
5156
5157 assert!(!CssPropertyCache::check_properties_changed(
5159 &NodeData::create_div(),
5160 &plain,
5161 &hovered
5162 ));
5163 }
5164
5165 #[test]
5166 fn check_layout_properties_changed_ignores_non_layout_properties() {
5167 let plain = DynamicSelectorContext::default();
5168 let mut hovered = DynamicSelectorContext::default();
5169 hovered.pseudo_state.hover = true;
5170
5171 let layout = div_with_pseudo(vec![width_px(1.0)], PseudoStateType::Hover);
5172 assert!(CssPropertyCache::check_layout_properties_changed(
5173 &layout, &plain, &hovered
5174 ));
5175 assert!(CssPropertyType::Width.can_trigger_relayout());
5176
5177 let paint = div_with_pseudo(
5179 vec![CssProperty::const_none(CssPropertyType::BackgroundContent)],
5180 PseudoStateType::Hover,
5181 );
5182 assert!(!CssPropertyType::BackgroundContent.can_trigger_relayout());
5183 assert!(!CssPropertyCache::check_layout_properties_changed(
5184 &paint, &plain, &hovered
5185 ));
5186 assert!(CssPropertyCache::check_properties_changed(
5188 &paint, &plain, &hovered
5189 ));
5190 }
5191
5192 #[test]
5197 fn grid_gap_and_scrollbar_getters_are_none_on_a_bare_div() {
5198 let c = CssPropertyCache::empty(1);
5199 let nd = NodeData::create_div();
5200 assert!(c.get_grid_gap(&nd, &n0(), &normal()).is_none());
5201 assert!(c.get_scrollbar_track(&nd, &n0(), &normal()).is_none());
5202 assert!(c.get_scrollbar_thumb(&nd, &n0(), &normal()).is_none());
5203 assert!(c.get_scrollbar_button(&nd, &n0(), &normal()).is_none());
5204 assert!(c.get_scrollbar_corner(&nd, &n0(), &normal()).is_none());
5205 assert!(c.get_scrollbar_resizer(&nd, &n0(), &normal()).is_none());
5206
5207 let far = NodeId::new(4_242);
5209 assert!(c.get_grid_gap(&nd, &far, &normal()).is_none());
5210 assert!(c.get_scrollbar_thumb(&nd, &far, &normal()).is_none());
5211 }
5212
5213 #[test]
5218 fn computed_css_style_string_serializes_set_properties() {
5219 let c = CssPropertyCache::empty(1);
5220
5221 let s = c.get_computed_css_style_string(&NodeData::create_div(), &n0(), &normal());
5223 assert!(s.contains("display:"), "got {s:?}");
5224
5225 let styled = div_with(vec![width_px(100.0), font_size(PixelValue::px(12.0))]);
5226 let s = c.get_computed_css_style_string(&styled, &n0(), &normal());
5227 assert!(s.contains("width:"), "got {s:?}");
5228 assert!(s.contains("font-size:"), "got {s:?}");
5229 assert!(s.ends_with(';'), "each declaration is terminated: {s:?}");
5230 }
5231
5232 #[test]
5233 fn computed_css_style_string_does_not_panic_on_an_out_of_range_node_id() {
5234 let c = CssPropertyCache::empty(0);
5235 let s = c.get_computed_css_style_string(
5236 &NodeData::create_div(),
5237 &NodeId::new(usize::MAX / 2),
5238 &normal(),
5239 );
5240 assert!(s.contains("display:"));
5241 }
5242
5243 #[test]
5248 fn apply_ua_css_inserts_ua_properties_into_cascaded_props() {
5249 let nodes = vec![NodeData::create_div()];
5250 let mut c = CssPropertyCache::empty(1);
5251 c.apply_ua_css(&nodes);
5252
5253 let props = c.cascaded_props.build_get(0).expect("build phase");
5254 assert!(
5255 props
5256 .iter()
5257 .any(|p| p.prop_type == CssPropertyType::Display
5258 && p.state == PseudoStateType::Normal),
5259 "UA `div {{ display: block }}` must land in the cascade"
5260 );
5261 }
5262
5263 #[test]
5264 fn apply_ua_css_does_not_override_an_existing_inline_property() {
5265 let nodes = vec![div_with(vec![CssProperty::const_none(
5266 CssPropertyType::Display,
5267 )])];
5268 let mut c = CssPropertyCache::empty(1);
5269 c.apply_ua_css(&nodes);
5270
5271 let props = c.cascaded_props.build_get(0).expect("build phase");
5272 assert!(
5273 !props.iter().any(|p| p.prop_type == CssPropertyType::Display),
5274 "UA CSS is the weakest layer and must not clobber inline"
5275 );
5276 }
5277
5278 #[test]
5279 fn apply_ua_css_on_zero_nodes_returns_early() {
5280 let mut c = CssPropertyCache::empty(0);
5281 c.apply_ua_css(&[]);
5282 assert_eq!(c.cascaded_props.len(), 0);
5283 }
5284
5285 #[test]
5286 fn sort_cascaded_props_flattens_and_orders_by_state_then_type() {
5287 let mut c = CssPropertyCache::empty(1);
5288 c.cascaded_props
5289 .push_to(0, stateful(PseudoStateType::Hover, width_px(1.0)));
5290 c.cascaded_props.push_to(
5291 0,
5292 stateful(
5293 PseudoStateType::Normal,
5294 CssProperty::const_none(CssPropertyType::Display),
5295 ),
5296 );
5297 c.cascaded_props
5298 .push_to(0, stateful(PseudoStateType::Normal, width_px(2.0)));
5299
5300 c.sort_cascaded_props();
5301
5302 assert!(c.cascaded_props.is_flattened());
5303 let slice = c.cascaded_props.get_slice(0);
5304 assert_eq!(slice.len(), 3);
5305 let keys: Vec<_> = slice.iter().map(|p| (p.state, p.prop_type)).collect();
5306 let mut sorted = keys.clone();
5307 sorted.sort_unstable();
5308 assert_eq!(keys, sorted, "binary_search lookups require sort order");
5309 }
5310
5311 #[test]
5312 fn prune_compact_normal_props_keeps_what_the_slow_path_still_needs() {
5313 let mut c = CssPropertyCache::empty(1);
5314 c.cascaded_props.push_to(
5316 0,
5317 stateful(
5318 PseudoStateType::Normal,
5319 CssProperty::const_none(CssPropertyType::Display),
5320 ),
5321 );
5322 c.cascaded_props
5324 .push_to(0, stateful(PseudoStateType::Normal, width_pct(50.0)));
5325 c.cascaded_props.push_to(
5327 0,
5328 stateful(
5329 PseudoStateType::Normal,
5330 CssProperty::const_none(CssPropertyType::BackgroundContent),
5331 ),
5332 );
5333 c.cascaded_props.push_to(
5335 0,
5336 stateful(
5337 PseudoStateType::Hover,
5338 CssProperty::const_none(CssPropertyType::Display),
5339 ),
5340 );
5341
5342 c.prune_compact_normal_props();
5343
5344 let kept: Vec<(PseudoStateType, CssPropertyType)> = c
5345 .cascaded_props
5346 .get_slice(0)
5347 .iter()
5348 .map(|p| (p.state, p.prop_type))
5349 .collect();
5350
5351 assert!(
5352 !kept.contains(&(PseudoStateType::Normal, CssPropertyType::Display)),
5353 "the compact cache is authoritative for this one"
5354 );
5355 assert!(kept.contains(&(PseudoStateType::Normal, CssPropertyType::Width)));
5356 assert!(kept.contains(&(PseudoStateType::Normal, CssPropertyType::BackgroundContent)));
5357 assert!(kept.contains(&(PseudoStateType::Hover, CssPropertyType::Display)));
5358 assert_eq!(kept.len(), 3);
5359 }
5360
5361 #[test]
5362 fn prune_compact_normal_props_on_an_empty_cache_does_not_panic() {
5363 let mut c = CssPropertyCache::empty(0);
5364 c.prune_compact_normal_props();
5365 assert_eq!(c.cascaded_props.len(), 0);
5366
5367 let mut c = CssPropertyCache::empty(3);
5368 c.prune_compact_normal_props();
5369 assert_eq!(c.cascaded_props.len(), 3);
5370 assert!(c.cascaded_props.get_slice(0).is_empty());
5371 }
5372
5373 fn two_node_hierarchy() -> Vec<NodeHierarchyItem> {
5379 vec![
5380 NodeHierarchyItem {
5381 parent: 0,
5382 previous_sibling: 0,
5383 next_sibling: 0,
5384 last_child: 2,
5385 },
5386 NodeHierarchyItem {
5387 parent: 1,
5388 previous_sibling: 0,
5389 next_sibling: 0,
5390 last_child: 0,
5391 },
5392 ]
5393 }
5394
5395 #[test]
5396 fn compute_inherited_values_propagates_font_size_to_children() {
5397 let hierarchy = two_node_hierarchy();
5398 assert_eq!(hierarchy[1].parent_id(), Some(NodeId::new(0)));
5399
5400 let nodes = vec![
5401 div_with(vec![font_size(PixelValue::px(20.0))]),
5402 NodeData::create_div(),
5403 ];
5404 let mut c = CssPropertyCache::empty(2);
5405 let changed = c.compute_inherited_values(&hierarchy, &nodes);
5406
5407 assert_eq!(c.computed_values.len(), 2);
5408 assert_eq!(changed.len(), 2, "both nodes gained a computed value");
5409
5410 let (t, v) = &c.computed_values[1][0];
5411 assert_eq!(*t, CssPropertyType::FontSize);
5412 assert_eq!(v.origin, CssPropertyOrigin::Inherited);
5413 assert!(close(font_size_parts(&v.property).unwrap().1, 20.0));
5414
5415 assert_eq!(c.computed_values[0][0].1.origin, CssPropertyOrigin::Own);
5417 }
5418
5419 #[test]
5420 fn compute_inherited_values_resolves_a_child_em_against_the_parent_px() {
5421 let hierarchy = two_node_hierarchy();
5422 let nodes = vec![
5423 div_with(vec![font_size(PixelValue::px(20.0))]),
5424 div_with(vec![font_size(PixelValue::em(2.0))]),
5425 ];
5426 let mut c = CssPropertyCache::empty(2);
5427 c.compute_inherited_values(&hierarchy, &nodes);
5428
5429 let (t, v) = &c.computed_values[1][0];
5430 assert_eq!(*t, CssPropertyType::FontSize);
5431 assert_eq!(v.origin, CssPropertyOrigin::Own);
5432 let (metric, n) = font_size_parts(&v.property).unwrap();
5433 assert_eq!(metric, SizeMetric::Px, "resolved to absolute px");
5434 assert!(close(n, 40.0), "2em of the parent's 20px, got {n}");
5435 }
5436
5437 #[test]
5438 fn compute_inherited_values_is_idempotent_on_a_second_run() {
5439 let hierarchy = two_node_hierarchy();
5440 let nodes = vec![
5441 div_with(vec![font_size(PixelValue::px(20.0))]),
5442 NodeData::create_div(),
5443 ];
5444 let mut c = CssPropertyCache::empty(2);
5445 assert_eq!(c.compute_inherited_values(&hierarchy, &nodes).len(), 2);
5446 assert!(
5447 c.compute_inherited_values(&hierarchy, &nodes).is_empty(),
5448 "nothing changed the second time around"
5449 );
5450 }
5451
5452 #[test]
5453 fn compute_inherited_values_on_an_empty_tree_does_not_panic() {
5454 let mut c = CssPropertyCache::empty(0);
5455 assert!(c.compute_inherited_values(&[], &[]).is_empty());
5456 assert!(c.computed_values.is_empty());
5457 }
5458
5459 fn one_node_scaffold() -> (NodeHierarchyItemVec, NodeDataContainer<CascadeInfo>) {
5464 (
5465 vec![NodeHierarchyItem::zeroed()].into(),
5466 NodeDataContainer::new(vec![CascadeInfo {
5467 index_in_parent: 0,
5468 is_last_child: true,
5469 }]),
5470 )
5471 }
5472
5473 #[test]
5474 fn restyle_with_an_empty_stylesheet_flattens_and_yields_no_tags() {
5475 let (hierarchy, cascade) = one_node_scaffold();
5476 let nodes = NodeDataContainer::new(vec![NodeData::create_div()]);
5477 let non_leaf: ParentWithNodeDepthVec = Vec::new().into();
5478 let mut css = Css::empty();
5479
5480 let mut c = CssPropertyCache::empty(1);
5481 let tags = c.restyle(
5482 &mut css,
5483 &nodes.as_ref(),
5484 &hierarchy,
5485 &non_leaf,
5486 &cascade.as_ref(),
5487 );
5488
5489 assert!(tags.is_empty(), "a plain div needs no hit-test tag");
5490 assert!(
5491 c.css_props.is_flattened(),
5492 "restyle must leave css_props in read phase"
5493 );
5494 assert!(c.resolved_font_sizes_px.get().is_none());
5495 }
5496
5497 #[test]
5498 fn generate_tag_ids_skips_inert_nodes_and_tags_interactive_ones() {
5499 let (hierarchy, _) = one_node_scaffold();
5500
5501 let inert = NodeDataContainer::new(vec![NodeData::create_div()]);
5502 let c = CssPropertyCache::empty(1);
5503 assert!(c.generate_tag_ids(&inert.as_ref(), &hierarchy).is_empty());
5504
5505 let hoverable = NodeDataContainer::new(vec![div_with_pseudo(
5507 vec![width_px(1.0)],
5508 PseudoStateType::Hover,
5509 )]);
5510 let tags = c.generate_tag_ids(&hoverable.as_ref(), &hierarchy);
5511 assert_eq!(tags.len(), 1);
5512 assert_eq!(
5513 tags[0].node_id.into_crate_internal(),
5514 Some(NodeId::new(0))
5515 );
5516 }
5517
5518 #[test]
5519 fn generate_tag_ids_tags_a_node_with_a_cursor_declaration() {
5520 let (hierarchy, _) = one_node_scaffold();
5521 let nodes = NodeDataContainer::new(vec![div_with(vec![CssProperty::const_none(
5522 CssPropertyType::Cursor,
5523 )])]);
5524 let c = CssPropertyCache::empty(1);
5525 assert_eq!(c.generate_tag_ids(&nodes.as_ref(), &hierarchy).len(), 1);
5526 }
5527
5528 #[test]
5529 fn generate_tag_ids_on_an_empty_dom_yields_nothing() {
5530 let nodes: NodeDataContainer<NodeData> = NodeDataContainer::new(Vec::new());
5531 let hierarchy: NodeHierarchyItemVec = Vec::new().into();
5532 let c = CssPropertyCache::empty(0);
5533 assert!(c.generate_tag_ids(&nodes.as_ref(), &hierarchy).is_empty());
5534 }
5535
5536 #[cfg(feature = "std")]
5541 #[test]
5542 fn css_prop_type_label_is_interned_and_distinct_per_variant() {
5543 let a = CssPropertyCache::css_prop_type_label(&CssPropertyType::Width);
5544 let b = CssPropertyCache::css_prop_type_label(&CssPropertyType::Width);
5545 assert!(!a.is_empty());
5546 assert_eq!(
5547 a.as_ptr(),
5548 b.as_ptr(),
5549 "the label table must leak at most one &'static str per variant"
5550 );
5551
5552 let other = CssPropertyCache::css_prop_type_label(&CssPropertyType::Height);
5553 assert_ne!(a, other);
5554 }
5555
5556 #[cfg(feature = "std")]
5557 #[test]
5558 fn drain_css_prop_counts_is_sorted_descending_and_drains() {
5559 let first = drain_css_prop_counts();
5562 for w in first.windows(2) {
5563 assert!(w[0].1 >= w[1].1, "counts must be sorted descending");
5564 }
5565 assert!(
5566 drain_css_prop_counts().is_empty(),
5567 "a drained counter comes back empty"
5568 );
5569 }
5570}