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