extern crate alloc;
use alloc::{boxed::Box, string::String, vec::Vec};
use core::fmt::Write;
use core::mem::ManuallyDrop;
use crate::dom::NodeType;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum CssPropertyOrigin {
Inherited,
Own,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CssPropertyWithOrigin {
pub property: CssProperty,
pub origin: CssPropertyOrigin,
}
use azul_css::{
css::{Css, CssPath},
props::{
basic::{StyleFontFamily, StyleFontFamilyVec, StyleFontSize},
layout::{LayoutDisplay, LayoutHeight, LayoutWidth},
property::{
BoxDecorationBreakValue, BreakInsideValue, CaretAnimationDurationValue,
CaretColorValue, CaretWidthValue, ClipPathValue, ColumnCountValue, ColumnFillValue,
ColumnRuleColorValue, ColumnRuleStyleValue, ColumnRuleWidthValue, ColumnSpanValue,
ColumnWidthValue, ContentValue, CounterIncrementValue, CounterResetValue, CssProperty,
CssPropertyType, FlowFromValue, FlowIntoValue, LayoutAlignContentValue,
LayoutAlignItemsValue, LayoutAlignSelfValue, LayoutBorderBottomWidthValue,
LayoutBorderLeftWidthValue, LayoutBorderRightWidthValue, LayoutBorderSpacingValue,
LayoutBorderTopWidthValue, LayoutBoxSizingValue, LayoutClearValue,
LayoutColumnGapValue, LayoutDisplayValue, LayoutFlexBasisValue,
LayoutFlexDirectionValue, LayoutFlexGrowValue, LayoutFlexShrinkValue,
LayoutFlexWrapValue, LayoutFloatValue, LayoutGapValue, LayoutGridAutoColumnsValue,
LayoutGridAutoFlowValue, LayoutGridAutoRowsValue, LayoutGridColumnValue,
LayoutGridRowValue, LayoutGridTemplateColumnsValue, LayoutGridTemplateRowsValue,
LayoutHeightValue, LayoutInsetBottomValue, LayoutJustifyContentValue,
LayoutJustifyItemsValue, LayoutJustifySelfValue, LayoutLeftValue,
LayoutMarginBottomValue, LayoutMarginLeftValue, LayoutMarginRightValue,
LayoutMarginTopValue, LayoutMaxHeightValue, LayoutMaxWidthValue, LayoutMinHeightValue,
LayoutMinWidthValue, LayoutOverflowValue, LayoutPaddingBottomValue,
LayoutPaddingLeftValue, LayoutPaddingRightValue, LayoutPaddingTopValue,
LayoutPositionValue, LayoutRightValue, LayoutRowGapValue, LayoutScrollbarWidthValue,
LayoutTableLayoutValue, LayoutTextJustifyValue, LayoutTopValue, LayoutWidthValue,
LayoutWritingModeValue, LayoutZIndexValue, OrphansValue, PageBreakValue,
StyleBackgroundContentValue, ScrollbarFadeDelayValue, ScrollbarFadeDurationValue,
ScrollbarVisibilityModeValue, SelectionBackgroundColorValue, SelectionColorValue,
SelectionRadiusValue, ShapeImageThresholdValue, ShapeInsideValue, ShapeMarginValue,
ShapeOutsideValue, StringSetValue, StyleBackfaceVisibilityValue,
StyleBackgroundContentVecValue, StyleBackgroundPositionVecValue,
StyleBackgroundRepeatVecValue, StyleBackgroundSizeVecValue,
StyleBorderBottomColorValue, StyleBorderBottomLeftRadiusValue,
StyleBorderBottomRightRadiusValue, StyleBorderBottomStyleValue,
StyleBorderCollapseValue, StyleBorderLeftColorValue, StyleBorderLeftStyleValue,
StyleBorderRightColorValue, StyleBorderRightStyleValue, StyleBorderTopColorValue,
StyleBorderTopLeftRadiusValue, StyleBorderTopRightRadiusValue,
StyleBorderTopStyleValue, StyleBoxShadowValue, StyleCaptionSideValue, StyleCursorValue,
StyleDirectionValue, StyleEmptyCellsValue, StyleExclusionMarginValue,
StyleFilterVecValue, StyleFontFamilyVecValue, StyleFontSizeValue, StyleFontStyleValue,
StyleFontValue, StyleFontWeightValue, StyleHangingPunctuationValue,
StyleHyphenationLanguageValue, StyleHyphensValue, StyleInitialLetterValue,
StyleLetterSpacingValue, StyleLineBreakValue, StyleLineClampValue, StyleLineHeightValue,
StyleListStylePositionValue, StyleListStyleTypeValue, StyleMixBlendModeValue,
StyleAspectRatioValue, StyleObjectFitValue, StyleObjectPositionValue, StyleTextOverflowValue,
StyleOpacityValue, StylePerspectiveOriginValue,
StyleScrollbarColorValue, StyleOverflowWrapValue, StyleTabSizeValue,
StyleTextAlignLastValue, StyleTextOrientationValue, StyleTextTransformValue,
StyleTextAlignValue, StyleTextColorValue,
StyleTextCombineUprightValue, StyleUnicodeBidiValue,
StyleTextBoxTrimValue, StyleTextBoxEdgeValue,
StyleDominantBaselineValue, StyleAlignmentBaselineValue, StyleBaselineSourceValue,
StyleLineFitEdgeValue,
StyleInitialLetterAlignValue, StyleInitialLetterWrapValue,
StyleScrollbarGutterValue, StyleOverflowClipMarginValue, StyleClipRectValue,
StyleTextDecorationValue, StyleTextIndentValue,
StyleTransformOriginValue, StyleTransformVecValue, StyleUserSelectValue,
StyleVerticalAlignValue, StyleVisibilityValue, StyleWhiteSpaceValue,
StyleWordBreakValue, StyleWordSpacingValue, WidowsValue,
},
style::{StyleCursor, StyleTextColor, StyleTransformOrigin},
},
AzString,
};
use crate::{
dom::{NodeData, NodeId, TabIndex, TagId},
id::{NodeDataContainer, NodeDataContainerRef},
style::CascadeInfo,
styled_dom::{
NodeHierarchyItem, NodeHierarchyItemId, NodeHierarchyItemVec, ParentWithNodeDepth,
ParentWithNodeDepthVec, StyledNodeState, TagIdToNodeIdMapping,
},
};
use azul_css::dynamic_selector::{
CssPropertyWithConditions, CssPropertyWithConditionsVec, DynamicSelectorContext,
};
#[cfg(feature = "std")]
std::thread_local! {
static PROP_COUNTS: core::cell::RefCell<
std::collections::HashMap<&'static str, usize>
> = core::cell::RefCell::new(std::collections::HashMap::new());
}
#[cfg(feature = "std")]
#[must_use] pub fn drain_css_prop_counts() -> Vec<(&'static str, usize)> {
PROP_COUNTS
.try_with(|c| {
let map = core::mem::take(&mut *c.borrow_mut());
let mut v: Vec<_> = map.into_iter().collect();
v.sort_by(|a, b| b.1.cmp(&a.1));
v
})
.unwrap_or_default()
}
const PT_TO_PX: f32 = 1.333_333;
const IN_TO_PX: f32 = 96.0;
const CM_TO_PX: f32 = 37.795_277;
const MM_TO_PX: f32 = 3.779_527_7;
#[allow(unused_macros)]
macro_rules! match_property_value {
($property:expr, $value:ident, $expr:expr) => {
match $property {
CssProperty::CaretColor($value) => $expr,
CssProperty::CaretAnimationDuration($value) => $expr,
CssProperty::SelectionBackgroundColor($value) => $expr,
CssProperty::SelectionColor($value) => $expr,
CssProperty::SelectionRadius($value) => $expr,
CssProperty::TextColor($value) => $expr,
CssProperty::FontSize($value) => $expr,
CssProperty::FontFamily($value) => $expr,
CssProperty::FontWeight($value) => $expr,
CssProperty::FontStyle($value) => $expr,
CssProperty::TextAlign($value) => $expr,
CssProperty::TextJustify($value) => $expr,
CssProperty::VerticalAlign($value) => $expr,
CssProperty::LetterSpacing($value) => $expr,
CssProperty::TextIndent($value) => $expr,
CssProperty::InitialLetter($value) => $expr,
CssProperty::LineClamp($value) => $expr,
CssProperty::HangingPunctuation($value) => $expr,
CssProperty::TextCombineUpright($value) => $expr,
CssProperty::UnicodeBidi($value) => $expr,
CssProperty::TextBoxTrim($value) => $expr,
CssProperty::TextBoxEdge($value) => $expr,
CssProperty::DominantBaseline($value) => $expr,
CssProperty::AlignmentBaseline($value) => $expr,
CssProperty::BaselineSource($value) => $expr,
CssProperty::LineFitEdge($value) => $expr,
CssProperty::InitialLetterAlign($value) => $expr,
CssProperty::InitialLetterWrap($value) => $expr,
CssProperty::ScrollbarGutter($value) => $expr,
CssProperty::OverflowClipMargin($value) => $expr,
CssProperty::Clip($value) => $expr,
CssProperty::ExclusionMargin($value) => $expr,
CssProperty::HyphenationLanguage($value) => $expr,
CssProperty::LineHeight($value) => $expr,
CssProperty::WordSpacing($value) => $expr,
CssProperty::TabSize($value) => $expr,
CssProperty::WhiteSpace($value) => $expr,
CssProperty::Hyphens($value) => $expr,
CssProperty::Direction($value) => $expr,
CssProperty::UserSelect($value) => $expr,
CssProperty::TextDecoration($value) => $expr,
CssProperty::Cursor($value) => $expr,
CssProperty::Display($value) => $expr,
CssProperty::Float($value) => $expr,
CssProperty::BoxSizing($value) => $expr,
CssProperty::Width($value) => $expr,
CssProperty::Height($value) => $expr,
CssProperty::MinWidth($value) => $expr,
CssProperty::MinHeight($value) => $expr,
CssProperty::MaxWidth($value) => $expr,
CssProperty::MaxHeight($value) => $expr,
CssProperty::Position($value) => $expr,
CssProperty::Top($value) => $expr,
CssProperty::Right($value) => $expr,
CssProperty::Left($value) => $expr,
CssProperty::Bottom($value) => $expr,
CssProperty::ZIndex($value) => $expr,
CssProperty::FlexWrap($value) => $expr,
CssProperty::FlexDirection($value) => $expr,
CssProperty::FlexGrow($value) => $expr,
CssProperty::FlexShrink($value) => $expr,
CssProperty::FlexBasis($value) => $expr,
CssProperty::JustifyContent($value) => $expr,
CssProperty::AlignItems($value) => $expr,
CssProperty::AlignContent($value) => $expr,
CssProperty::AlignSelf($value) => $expr,
CssProperty::JustifyItems($value) => $expr,
CssProperty::JustifySelf($value) => $expr,
CssProperty::BackgroundContent($value) => $expr,
CssProperty::BackgroundPosition($value) => $expr,
CssProperty::BackgroundSize($value) => $expr,
CssProperty::BackgroundRepeat($value) => $expr,
CssProperty::OverflowX($value) => $expr,
CssProperty::OverflowY($value) => $expr,
CssProperty::OverflowBlock($value) => $expr,
CssProperty::OverflowInline($value) => $expr,
CssProperty::PaddingTop($value) => $expr,
CssProperty::PaddingLeft($value) => $expr,
CssProperty::PaddingRight($value) => $expr,
CssProperty::PaddingBottom($value) => $expr,
CssProperty::MarginTop($value) => $expr,
CssProperty::MarginLeft($value) => $expr,
CssProperty::MarginRight($value) => $expr,
CssProperty::MarginBottom($value) => $expr,
CssProperty::BorderTopLeftRadius($value) => $expr,
CssProperty::BorderTopRightRadius($value) => $expr,
CssProperty::BorderBottomLeftRadius($value) => $expr,
CssProperty::BorderBottomRightRadius($value) => $expr,
CssProperty::BorderTopColor($value) => $expr,
CssProperty::BorderRightColor($value) => $expr,
CssProperty::BorderLeftColor($value) => $expr,
CssProperty::BorderBottomColor($value) => $expr,
CssProperty::BorderTopStyle($value) => $expr,
CssProperty::BorderRightStyle($value) => $expr,
CssProperty::BorderLeftStyle($value) => $expr,
CssProperty::BorderBottomStyle($value) => $expr,
CssProperty::BorderTopWidth($value) => $expr,
CssProperty::BorderRightWidth($value) => $expr,
CssProperty::BorderLeftWidth($value) => $expr,
CssProperty::BorderBottomWidth($value) => $expr,
CssProperty::BoxShadow($value) => $expr,
CssProperty::Opacity($value) => $expr,
CssProperty::Transform($value) => $expr,
CssProperty::TransformOrigin($value) => $expr,
CssProperty::PerspectiveOrigin($value) => $expr,
CssProperty::BackfaceVisibility($value) => $expr,
CssProperty::MixBlendMode($value) => $expr,
CssProperty::Filter($value) => $expr,
CssProperty::Visibility($value) => $expr,
CssProperty::WritingMode($value) => $expr,
CssProperty::GridTemplateColumns($value) => $expr,
CssProperty::GridTemplateRows($value) => $expr,
CssProperty::GridAutoColumns($value) => $expr,
CssProperty::GridAutoRows($value) => $expr,
CssProperty::GridAutoFlow($value) => $expr,
CssProperty::GridColumn($value) => $expr,
CssProperty::GridRow($value) => $expr,
CssProperty::GridTemplateAreas($value) => $expr,
CssProperty::Gap($value) => $expr,
CssProperty::ColumnGap($value) => $expr,
CssProperty::RowGap($value) => $expr,
CssProperty::Clear($value) => $expr,
CssProperty::ScrollbarTrack($value) => $expr,
CssProperty::ScrollbarThumb($value) => $expr,
CssProperty::ScrollbarButton($value) => $expr,
CssProperty::ScrollbarCorner($value) => $expr,
CssProperty::ScrollbarResizer($value) => $expr,
CssProperty::ScrollbarWidth($value) => $expr,
CssProperty::ScrollbarColor($value) => $expr,
CssProperty::ListStyleType($value) => $expr,
CssProperty::ListStylePosition($value) => $expr,
CssProperty::Font($value) => $expr,
CssProperty::ColumnCount($value) => $expr,
CssProperty::ColumnWidth($value) => $expr,
CssProperty::ColumnSpan($value) => $expr,
CssProperty::ColumnFill($value) => $expr,
CssProperty::ColumnRuleStyle($value) => $expr,
CssProperty::ColumnRuleWidth($value) => $expr,
CssProperty::ColumnRuleColor($value) => $expr,
CssProperty::FlowInto($value) => $expr,
CssProperty::FlowFrom($value) => $expr,
CssProperty::ShapeOutside($value) => $expr,
CssProperty::ShapeInside($value) => $expr,
CssProperty::ShapeImageThreshold($value) => $expr,
CssProperty::ShapeMargin($value) => $expr,
CssProperty::ClipPath($value) => $expr,
CssProperty::Content($value) => $expr,
CssProperty::CounterIncrement($value) => $expr,
CssProperty::CounterReset($value) => $expr,
CssProperty::StringSet($value) => $expr,
CssProperty::Orphans($value) => $expr,
CssProperty::Widows($value) => $expr,
CssProperty::PageBreakBefore($value) => $expr,
CssProperty::PageBreakAfter($value) => $expr,
CssProperty::PageBreakInside($value) => $expr,
CssProperty::BreakInside($value) => $expr,
CssProperty::BoxDecorationBreak($value) => $expr,
CssProperty::TableLayout($value) => $expr,
CssProperty::BorderCollapse($value) => $expr,
CssProperty::BorderSpacing($value) => $expr,
CssProperty::CaptionSide($value) => $expr,
CssProperty::EmptyCells($value) => $expr,
}
};
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StatefulCssProperty {
pub state: azul_css::dynamic_selector::PseudoStateType,
pub prop_type: CssPropertyType,
pub property: CssProperty,
}
#[derive(Debug, Clone)]
pub struct FlatVecVec<T> {
build: Vec<Vec<T>>,
data: Vec<T>,
offsets: Vec<(u32, u32)>,
}
impl<T: PartialEq> PartialEq for FlatVecVec<T> {
fn eq(&self, other: &Self) -> bool {
let self_in_build = !self.build.is_empty() && self.offsets.is_empty();
let other_in_build = !other.build.is_empty() && other.offsets.is_empty();
debug_assert!(
self_in_build == other_in_build,
"FlatVecVec::eq called across phases (one build, one flattened)"
);
if self_in_build || other_in_build {
self.build == other.build
} else {
self.data == other.data && self.offsets == other.offsets
}
}
}
impl<T> Default for FlatVecVec<T> {
fn default() -> Self {
Self {
build: Vec::new(),
data: Vec::new(),
offsets: Vec::new(),
}
}
}
impl<T> FlatVecVec<T> {
#[must_use] pub fn heap_bytes(&self, per_element_size: usize) -> usize {
let data_bytes = self.data.capacity() * per_element_size;
let offsets_bytes =
self.offsets.capacity() * size_of::<(u32, u32)>();
let mut build_bytes = self.build.capacity() * size_of::<Vec<T>>();
for v in &self.build {
build_bytes += v.capacity() * per_element_size;
}
data_bytes + offsets_bytes + build_bytes
}
#[must_use] pub fn new(node_count: usize) -> Self {
let mut build = Vec::with_capacity(node_count);
for _ in 0..node_count {
build.push(Vec::new());
}
Self {
build,
data: Vec::new(),
offsets: Vec::new(),
}
}
#[inline]
pub fn push_to(&mut self, node_index: usize, item: T) {
self.build[node_index].push(item);
}
#[inline]
pub fn build_mut(&mut self, node_index: usize) -> &mut Vec<T> {
&mut self.build[node_index]
}
#[inline]
pub fn build_iter_mut(&mut self) -> core::slice::IterMut<'_, Vec<T>> {
self.build.iter_mut()
}
#[inline]
#[must_use] pub fn build_get(&self, node_index: usize) -> Option<&Vec<T>> {
self.build.get(node_index)
}
#[inline]
#[must_use] pub const fn len(&self) -> usize {
if self.offsets.is_empty() {
self.build.len()
} else {
self.offsets.len()
}
}
#[inline]
#[must_use] pub const fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline]
#[must_use] pub const fn is_flattened(&self) -> bool {
!self.offsets.is_empty() || self.build.is_empty()
}
#[inline]
#[must_use] pub fn get_slice(&self, node_index: usize) -> &[T] {
if self.offsets.is_empty() {
self.build.get(node_index).map_or(&[], alloc::vec::Vec::as_slice)
} else {
if let Some(&(start, len)) = self.offsets.get(node_index) {
let s = start as usize;
let l = len as usize;
&self.data[s..s + l]
} else {
&[]
}
}
}
pub fn sort_each_and_flatten<K: Ord + Eq>(&mut self, key_fn: impl Fn(&T) -> K) {
let node_count = self.build.len();
let total: usize = self.build.iter().map(alloc::vec::Vec::len).sum();
let mut flat_data = Vec::with_capacity(total);
let mut offsets = Vec::with_capacity(node_count);
for inner in &mut self.build {
inner.sort_by_key(|a| key_fn(a));
let n = inner.len();
let mut keep = vec![false; n];
for i in 0..n {
if i + 1 >= n || key_fn(&inner[i]) != key_fn(&inner[i + 1]) {
keep[i] = true;
}
}
let start = u32::try_from(flat_data.len()).unwrap_or(u32::MAX);
for (i, item) in inner.drain(..).enumerate() {
if keep[i] {
flat_data.push(item);
}
}
let len = u32::try_from(flat_data.len()).unwrap_or(u32::MAX) - start;
offsets.push((start, len));
}
flat_data.shrink_to_fit();
self.data = flat_data;
self.offsets = offsets;
self.build = Vec::new();
}
pub fn flatten(&mut self) {
let node_count = self.build.len();
let total: usize = self.build.iter().map(alloc::vec::Vec::len).sum();
let mut flat_data = Vec::with_capacity(total);
let mut offsets = Vec::with_capacity(node_count);
for inner in &mut self.build {
let start = u32::try_from(flat_data.len()).unwrap_or(u32::MAX);
let len = u32::try_from(inner.len()).unwrap_or(u32::MAX);
offsets.push((start, len));
flat_data.append(inner);
}
self.data = flat_data;
self.offsets = offsets;
self.build = Vec::new();
}
pub fn retain(&mut self, predicate: impl Fn(&T) -> bool) where T: Clone {
if self.offsets.is_empty() { return; }
let node_count = self.offsets.len();
let mut new_data = Vec::new();
let mut new_offsets = Vec::with_capacity(node_count);
for &(start, len) in &self.offsets {
let s = start as usize;
let l = len as usize;
let new_start = u32::try_from(new_data.len()).unwrap_or(u32::MAX);
let slice = &self.data[s..s + l];
let mut kept = 0u32;
for item in slice {
if predicate(item) {
new_data.push((*item).clone());
kept += 1;
}
}
new_offsets.push((new_start, kept));
}
new_data.shrink_to_fit();
self.data = new_data;
self.offsets = new_offsets;
}
pub fn ensure_build_phase(&mut self) where T: Clone {
if self.offsets.is_empty() {
return; }
let mut build = Vec::with_capacity(self.offsets.len());
for &(start, len) in &self.offsets {
let s = start as usize;
let l = len as usize;
build.push(self.data[s..s + l].to_vec());
}
self.build = build;
self.data = Vec::new();
self.offsets = Vec::new();
}
pub fn retain_with_node_index(
&mut self,
predicate: impl Fn(usize, &T) -> bool,
) where T: Clone {
if self.offsets.is_empty() { return; }
let node_count = self.offsets.len();
let mut new_data = Vec::new();
let mut new_offsets = Vec::with_capacity(node_count);
for (node_idx, &(start, len)) in self.offsets.iter().enumerate() {
let s = start as usize;
let l = len as usize;
let new_start = u32::try_from(new_data.len()).unwrap_or(u32::MAX);
let slice = &self.data[s..s + l];
let mut kept = 0u32;
for item in slice {
if predicate(node_idx, item) {
new_data.push((*item).clone());
kept += 1;
}
}
new_offsets.push((new_start, kept));
}
new_data.shrink_to_fit();
self.data = new_data;
self.offsets = new_offsets;
}
pub(crate) const fn iter_node_slices(&self) -> FlatVecVecIter<'_, T> {
FlatVecVecIter {
fvv: self,
idx: 0,
count: self.len(),
}
}
pub fn extend_from(&mut self, other: &mut Self) {
if !self.offsets.is_empty() && !other.offsets.is_empty() {
let base = u32::try_from(self.data.len()).unwrap_or(u32::MAX);
self.data.append(&mut other.data);
self.offsets.extend(other.offsets.drain(..).map(|(s, l)| (s + base, l)));
} else {
self.build.append(&mut other.build);
self.data.clear();
self.offsets.clear();
}
}
}
pub(crate) struct FlatVecVecIter<'a, T> {
fvv: &'a FlatVecVec<T>,
idx: usize,
count: usize,
}
impl<'a, T> Iterator for FlatVecVecIter<'a, T> {
type Item = (usize, &'a [T]);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.idx >= self.count {
return None;
}
let i = self.idx;
self.idx += 1;
Some((i, self.fvv.get_slice(i)))
}
fn size_hint(&self) -> (usize, Option<usize>) {
let rem = self.count - self.idx;
(rem, Some(rem))
}
}
impl<T> ExactSizeIterator for FlatVecVecIter<'_, T> {}
#[derive(Debug, Default, Clone, PartialEq)]
pub struct CssPropertyCache {
pub node_count: usize,
pub retained_author_css: Css,
pub user_overridden_properties: Vec<Vec<(CssPropertyType, CssProperty)>>,
pub cascaded_props: FlatVecVec<StatefulCssProperty>,
pub css_props: FlatVecVec<StatefulCssProperty>,
pub computed_values: Vec<Vec<(CssPropertyType, CssPropertyWithOrigin)>>,
pub compact_cache: Option<azul_css::compact_cache::CompactLayoutCache>,
pub global_css_props: Vec<CssProperty>,
pub resolved_font_sizes_px: crate::sync::OnceLock<Vec<f32>>,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CssPropertyCacheBreakdown {
pub node_count: usize,
pub cascaded_props_bytes: usize,
pub css_props_bytes: usize,
pub computed_values_bytes: usize,
pub user_overridden_bytes: usize,
pub global_css_props_bytes: usize,
pub compact_cache_bytes: usize,
pub resolved_font_sizes_bytes: usize,
}
impl CssPropertyCacheBreakdown {
#[must_use] pub const fn total_bytes(&self) -> usize {
self.cascaded_props_bytes
+ self.css_props_bytes
+ self.computed_values_bytes
+ self.user_overridden_bytes
+ self.global_css_props_bytes
+ self.compact_cache_bytes
+ self.resolved_font_sizes_bytes
}
}
impl CssPropertyCache {
pub fn memory_breakdown(&self) -> CssPropertyCacheBreakdown {
let stateful_sz = size_of::<StatefulCssProperty>();
let computed_entry_sz =
size_of::<(CssPropertyType, CssPropertyWithOrigin)>();
let outer_vec_sz = size_of::<Vec<(CssPropertyType, CssPropertyWithOrigin)>>();
let cascaded_bytes = self.cascaded_props.heap_bytes(stateful_sz);
let css_bytes = self.css_props.heap_bytes(stateful_sz);
let mut computed_bytes = self.computed_values.capacity() * outer_vec_sz;
for v in &self.computed_values {
computed_bytes += v.capacity() * computed_entry_sz;
}
let user_overridden_bytes = {
let mut b = self.user_overridden_properties.capacity() * outer_vec_sz;
for v in &self.user_overridden_properties {
b += v.capacity()
* size_of::<(CssPropertyType, CssProperty)>();
}
b
};
let global_bytes = self.global_css_props.capacity()
* size_of::<CssProperty>();
let compact_bytes = self
.compact_cache
.as_ref()
.map_or(0, |c| {
c.tier1_enums.capacity() * 8
+ c.tier2_dims.capacity() * 68
+ c.tier2_cold.capacity() * 28
+ c.tier2b_text.capacity() * 24
+ c.prev_font_hashes.capacity() * 8
+ c.font_dirty_nodes.capacity() * 8
});
let resolved_font_sizes_bytes = self
.resolved_font_sizes_px
.get()
.map_or(0, |v| v.capacity() * size_of::<f32>());
CssPropertyCacheBreakdown {
node_count: self.node_count,
cascaded_props_bytes: cascaded_bytes,
css_props_bytes: css_bytes,
computed_values_bytes: computed_bytes,
user_overridden_bytes,
global_css_props_bytes: global_bytes,
compact_cache_bytes: compact_bytes,
resolved_font_sizes_bytes,
}
}
pub fn prune_compact_normal_props(&mut self) {
use azul_css::dynamic_selector::PseudoStateType;
#[cfg(feature = "std")]
{
static PRUNE_DBG: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
let dbg = *PRUNE_DBG.get_or_init(crate::profile::memory_enabled);
if dbg {
let mut normal_compact = 0usize;
let mut normal_noncompact = 0usize;
let mut nonnormal = 0usize;
for i in 0..self.css_props.len() {
for p in self.css_props.get_slice(i) {
if p.state != PseudoStateType::Normal {
nonnormal += 1;
} else if p.prop_type.has_compact_encoding() {
normal_compact += 1;
} else {
normal_noncompact += 1;
}
}
}
let ssp_sz = size_of::<StatefulCssProperty>();
let mut casc_normal_compact = 0usize;
let mut casc_total = 0usize;
for i in 0..self.cascaded_props.len() {
for p in self.cascaded_props.get_slice(i) {
casc_total += 1;
if p.state == PseudoStateType::Normal && p.prop_type.has_compact_encoding() {
casc_normal_compact += 1;
}
}
}
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}");
}
}
let keep = |p: &StatefulCssProperty| -> bool {
if p.state != PseudoStateType::Normal {
return true;
}
if !p.prop_type.has_compact_encoding() {
return true;
}
if property_needs_slow_path_after_compact(&p.property) {
return true;
}
false
};
if !self.cascaded_props.is_flattened() {
self.cascaded_props.sort_each_and_flatten(|p| (p.state, p.prop_type));
}
self.cascaded_props.retain(keep);
}
#[inline]
#[allow(clippy::trivially_copy_pass_by_ref)]
fn find_in_stateful<'a>(
props: &'a [StatefulCssProperty],
state: azul_css::dynamic_selector::PseudoStateType,
prop_type: &CssPropertyType,
) -> Option<&'a CssProperty> {
let key = (state, *prop_type);
props.binary_search_by_key(&key, |p| (p.state, p.prop_type))
.ok()
.map(|idx| &props[idx].property)
}
#[inline]
fn has_state_props(
props: &[StatefulCssProperty],
state: azul_css::dynamic_selector::PseudoStateType,
) -> bool {
let i = props.partition_point(|p| p.state < state);
i < props.len() && props[i].state == state
}
pub(crate) fn prop_types_for_state(
props: &[StatefulCssProperty],
state: azul_css::dynamic_selector::PseudoStateType,
) -> impl Iterator<Item = &CssPropertyType> + '_ {
props.iter().filter(move |p| p.state == state).map(|p| &p.prop_type)
}
}
fn property_needs_slow_path_after_compact(prop: &CssProperty) -> bool {
use azul_css::css::CssPropertyValue;
use azul_css::props::{
basic::length::SizeMetric,
layout::{
dimensions::{LayoutHeight, LayoutWidth},
flex::LayoutFlexBasis,
},
};
macro_rules! check_plain {
($v:expr) => {{
if let CssPropertyValue::Exact(ref inner) = $v {
return inner.inner.metric != SizeMetric::Px;
}
false
}};
}
match prop {
CssProperty::Width(v) => {
if let CssPropertyValue::Exact(LayoutWidth::Px(pv)) = v {
return pv.metric != SizeMetric::Px;
}
false
}
CssProperty::Height(v) => {
if let CssPropertyValue::Exact(LayoutHeight::Px(pv)) = v {
return pv.metric != SizeMetric::Px;
}
false
}
CssProperty::FlexBasis(v) => {
if let CssPropertyValue::Exact(LayoutFlexBasis::Exact(pv)) = v {
return pv.metric != SizeMetric::Px;
}
false
}
CssProperty::MinWidth(v) => check_plain!(v),
CssProperty::MaxWidth(v) => check_plain!(v),
CssProperty::MinHeight(v) => check_plain!(v),
CssProperty::MaxHeight(v) => check_plain!(v),
CssProperty::FontSize(v) => check_plain!(v),
CssProperty::PaddingTop(v) => check_plain!(v),
CssProperty::PaddingRight(v) => check_plain!(v),
CssProperty::PaddingBottom(v) => check_plain!(v),
CssProperty::PaddingLeft(v) => check_plain!(v),
CssProperty::MarginTop(v) => check_plain!(v),
CssProperty::MarginRight(v) => check_plain!(v),
CssProperty::MarginBottom(v) => check_plain!(v),
CssProperty::MarginLeft(v) => check_plain!(v),
CssProperty::BorderTopWidth(v) => check_plain!(v),
CssProperty::BorderRightWidth(v) => check_plain!(v),
CssProperty::BorderBottomWidth(v) => check_plain!(v),
CssProperty::BorderLeftWidth(v) => check_plain!(v),
CssProperty::Top(v) => check_plain!(v),
CssProperty::Right(v) => check_plain!(v),
CssProperty::Bottom(v) => check_plain!(v),
CssProperty::Left(v) => check_plain!(v),
CssProperty::ColumnGap(v) => check_plain!(v),
CssProperty::RowGap(v) => check_plain!(v),
CssProperty::LetterSpacing(v) => check_plain!(v),
CssProperty::WordSpacing(v) => check_plain!(v),
CssProperty::TextIndent(v) => check_plain!(v),
CssProperty::TabSize(v) => check_plain!(v),
_ => false,
}
}
fn is_resolved_parent_inherited(prop_type: CssPropertyType) -> bool {
prop_type == CssPropertyType::FontSize
}
fn clone_inheritable_property(
p: &CssProperty,
) -> CssProperty {
use azul_css::props::property::CssProperty;
if let CssProperty::FontFamily(v) = p { return CssProperty::FontFamily(v.clone()); }
if let CssProperty::BackgroundContent(v) = p { return CssProperty::BackgroundContent(v.clone()); }
if let CssProperty::BackgroundPosition(v) = p { return CssProperty::BackgroundPosition(v.clone()); }
if let CssProperty::BackgroundSize(v) = p { return CssProperty::BackgroundSize(v.clone()); }
if let CssProperty::BackgroundRepeat(v) = p { return CssProperty::BackgroundRepeat(v.clone()); }
if let CssProperty::BoxShadowLeft(v) = p { return CssProperty::BoxShadowLeft(v.clone()); }
if let CssProperty::BoxShadowRight(v) = p { return CssProperty::BoxShadowRight(v.clone()); }
if let CssProperty::BoxShadowTop(v) = p { return CssProperty::BoxShadowTop(v.clone()); }
if let CssProperty::BoxShadowBottom(v) = p { return CssProperty::BoxShadowBottom(v.clone()); }
if let CssProperty::TextShadow(v) = p { return CssProperty::TextShadow(v.clone()); }
if let CssProperty::ScrollbarTrack(v) = p { return CssProperty::ScrollbarTrack(v.clone()); }
if let CssProperty::ScrollbarThumb(v) = p { return CssProperty::ScrollbarThumb(v.clone()); }
if let CssProperty::ScrollbarButton(v) = p { return CssProperty::ScrollbarButton(v.clone()); }
if let CssProperty::ScrollbarCorner(v) = p { return CssProperty::ScrollbarCorner(v.clone()); }
if let CssProperty::ScrollbarResizer(v) = p { return CssProperty::ScrollbarResizer(v.clone()); }
if let CssProperty::Transform(v) = p { return CssProperty::Transform(v.clone()); }
if let CssProperty::Filter(v) = p { return CssProperty::Filter(v.clone()); }
if let CssProperty::BackdropFilter(v) = p { return CssProperty::BackdropFilter(v.clone()); }
if let CssProperty::Content(v) = p { return CssProperty::Content(v.clone()); }
if let CssProperty::HyphenationLanguage(v) = p { return CssProperty::HyphenationLanguage(v.clone()); }
if let CssProperty::Cursor(v) = p { return CssProperty::Cursor(*v); }
p.clone()
}
impl CssPropertyCache {
#[must_use]
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)] pub fn restyle(
&mut self,
css: &mut Css,
node_data: &NodeDataContainerRef<'_, NodeData>,
node_hierarchy: &NodeHierarchyItemVec,
non_leaf_nodes: &ParentWithNodeDepthVec,
html_tree: &NodeDataContainerRef<'_, CascadeInfo>,
) -> Vec<TagIdToNodeIdMapping> {
use azul_css::{
css::{CssDeclaration, CssPathPseudoSelector::{Hover, Active, Focus, Dragging, DragOver}, CssPathSelector, CssRuleBlock},
dynamic_selector::{DynamicSelector, PseudoStateType},
props::layout::LayoutDisplay,
};
let css_is_empty = css.is_empty();
if !css_is_empty {
css.sort_by_specificity();
let mut global_only_rules: Vec<&CssRuleBlock> = Vec::new();
let mut specific_rules: Vec<&CssRuleBlock> = Vec::new();
for rule in css.rules() {
let selectors = rule.path.selectors.as_ref();
let is_global_only = selectors.len() == 1
&& matches!(selectors.first(), Some(CssPathSelector::Global));
if is_global_only {
global_only_rules.push(rule);
} else {
specific_rules.push(rule);
}
}
let node_count = self.css_props.len();
self.css_props = FlatVecVec::new(node_count);
self.cascaded_props.ensure_build_phase();
self.global_css_props.clear();
for rule in &global_only_rules {
if crate::style::rule_ends_with(&rule.path, None) {
for d in &rule.declarations {
if let CssDeclaration::Static(s) = d {
self.global_css_props.push(s.clone());
}
}
}
}
if !specific_rules.is_empty() {
macro_rules! filter_rules {($expected_pseudo_selector:expr, $node_id:expr) => {{
let mut out: Vec<(u16, u16)> = Vec::new();
for (rule_idx, rule_block) in specific_rules.iter().enumerate() {
if !crate::style::rule_ends_with(&rule_block.path, $expected_pseudo_selector) {
continue;
}
if !crate::style::matches_html_element(
&rule_block.path,
$node_id,
&node_hierarchy.as_container(),
&node_data,
&html_tree,
$expected_pseudo_selector,
) {
continue;
}
for (decl_idx, decl) in rule_block.declarations.as_slice().iter().enumerate() {
if matches!(decl, CssDeclaration::Static(_)) {
out.push((u16::try_from(rule_idx).unwrap_or(u16::MAX), u16::try_from(decl_idx).unwrap_or(u16::MAX)));
}
}
}
out
}};}
let has_normal = specific_rules.iter().any(|r| crate::style::rule_ends_with(&r.path, None));
let has_hover = specific_rules.iter().any(|r| crate::style::rule_ends_with(&r.path, Some(Hover)));
let has_active = specific_rules.iter().any(|r| crate::style::rule_ends_with(&r.path, Some(Active)));
let has_focus = specific_rules.iter().any(|r| crate::style::rule_ends_with(&r.path, Some(Focus)));
let has_dragging = specific_rules.iter().any(|r| crate::style::rule_ends_with(&r.path, Some(Dragging)));
let has_drag_over = specific_rules.iter().any(|r| crate::style::rule_ends_with(&r.path, Some(DragOver)));
macro_rules! collect_and_assign {
($pseudo:expr, $state:expr, $has_any:expr) => {
if $has_any {
let indices: NodeDataContainer<(NodeId, Vec<(u16, u16)>)> = node_data
.transform_nodeid_optional(|node_id| {
let r = filter_rules!($pseudo, node_id);
if r.is_empty() { None } else { Some((node_id, r)) }
});
for (n, pairs) in indices.internal.into_iter() {
for (rule_idx, decl_idx) in pairs {
let decl = &specific_rules[rule_idx as usize]
.declarations
.as_slice()[decl_idx as usize];
if let CssDeclaration::Static(prop) = decl {
self.css_props.push_to(n.index(), StatefulCssProperty {
state: $state,
prop_type: prop.get_type(),
property: prop.clone(),
});
}
}
}
}
};
}
collect_and_assign!(None, PseudoStateType::Normal, has_normal);
collect_and_assign!(Some(Hover), PseudoStateType::Hover, has_hover);
collect_and_assign!(Some(Active), PseudoStateType::Active, has_active);
collect_and_assign!(Some(Focus), PseudoStateType::Focus, has_focus);
collect_and_assign!(Some(Dragging), PseudoStateType::Dragging, has_dragging);
collect_and_assign!(Some(DragOver), PseudoStateType::DragOver, has_drag_over);
} }
for ParentWithNodeDepth { depth: _, node_id } in non_leaf_nodes {
let Some(parent_id) = node_id.into_crate_internal() else {
continue;
};
let all_states = [
PseudoStateType::Normal,
PseudoStateType::Hover,
PseudoStateType::Active,
PseudoStateType::Focus,
PseudoStateType::Dragging,
PseudoStateType::DragOver,
];
for &state in &all_states {
let parent_inheritable_inline: Vec<(CssPropertyType, CssProperty)> = node_data[parent_id]
.style
.iter_inline_properties()
.filter(|(_prop, conds)| {
let conditions = conds.as_slice();
if conditions.is_empty() {
state == PseudoStateType::Normal
} else {
conditions.iter().all(|c| {
matches!(c, DynamicSelector::PseudoState(s) if *s == state)
})
}
})
.map(|(prop, _)| prop)
.filter(|prop| prop.get_type().is_inheritable() && !is_resolved_parent_inherited(prop.get_type()))
.map(|p| (p.get_type(), clone_inheritable_property(p)))
.collect();
let parent_inheritable_css: Vec<(CssPropertyType, CssProperty)> = if css_is_empty {
Vec::new()
} else {
self.css_props.get_slice(parent_id.index())
.iter()
.filter(|p| p.state == state && p.prop_type.is_inheritable() && !is_resolved_parent_inherited(p.prop_type))
.map(|p| (p.prop_type, clone_inheritable_property(&p.property)))
.collect()
};
let parent_inheritable_cascaded: Vec<(CssPropertyType, CssProperty)> =
self.cascaded_props.get_slice(parent_id.index())
.iter()
.filter(|p| p.state == state && p.prop_type.is_inheritable() && !is_resolved_parent_inherited(p.prop_type))
.map(|p| (p.prop_type, clone_inheritable_property(&p.property)))
.collect();
if parent_inheritable_inline.is_empty()
&& parent_inheritable_css.is_empty()
&& parent_inheritable_cascaded.is_empty()
{
continue;
}
for child_id in parent_id.az_children(&node_hierarchy.as_container()) {
let child_vec = self.cascaded_props.build_mut(child_id.index());
for (prop_type, prop_value) in parent_inheritable_inline
.iter()
.chain(parent_inheritable_css.iter())
.chain(parent_inheritable_cascaded.iter())
{
if !child_vec.iter().any(|p| p.state == state && p.prop_type == *prop_type) {
child_vec.push(StatefulCssProperty {
state,
prop_type: *prop_type,
property: prop_value.clone(),
});
}
}
}
}
}
self.css_props.sort_each_and_flatten(|p| (p.state, p.prop_type));
self.invalidate_resolved_font_sizes();
self.generate_tag_ids(node_data, node_hierarchy)
}
pub fn generate_tag_ids(
&self,
node_data: &NodeDataContainerRef<'_, NodeData>,
node_hierarchy: &NodeHierarchyItemVec,
) -> Vec<TagIdToNodeIdMapping> {
use azul_css::compact_cache::{
DISPLAY_SHIFT, DISPLAY_MASK,
OVERFLOW_X_SHIFT, OVERFLOW_Y_SHIFT, OVERFLOW_MASK,
};
let compact_cache = self.compact_cache.as_ref();
let node_data_container = &node_data.internal;
let tag_ids = node_data
.internal
.iter()
.enumerate()
.filter_map(|(node_idx, node_data)| {
let node_id = NodeId::new(node_idx);
let should_auto_insert_tabindex = node_data
.get_callbacks()
.iter()
.any(|cb| cb.event.is_focus_callback());
let tab_index = node_data.get_tab_index().map_or(if should_auto_insert_tabindex {
Some(TabIndex::Auto)
} else {
None
}, Some);
let mut need_tag = false;
'compute_need_tag: {
if let Some(cc) = compact_cache.as_ref() {
let t1 = cc.tier1_enums[node_idx];
let display_val = ((t1 >> DISPLAY_SHIFT) & DISPLAY_MASK) as u8;
if display_val == 4 { break 'compute_need_tag; } }
if node_data.has_context_menu() || node_data.get_context_menu().is_some() {
need_tag = true; break 'compute_need_tag;
}
if tab_index.is_some() { need_tag = true; break 'compute_need_tag; }
{
use azul_css::dynamic_selector::{DynamicSelector, PseudoStateType};
let has_pseudo = |state: PseudoStateType| -> bool {
node_data.style.iter_inline_properties().any(|(_p, conds)| {
conds.as_slice().iter().any(|c|
matches!(c, DynamicSelector::PseudoState(s) if *s == state)
)
}) || Self::has_state_props(self.css_props.get_slice(node_idx), state)
};
if has_pseudo(PseudoStateType::Hover)
|| has_pseudo(PseudoStateType::Active)
|| has_pseudo(PseudoStateType::Focus)
|| has_pseudo(PseudoStateType::Dragging)
|| has_pseudo(PseudoStateType::DragOver)
{
need_tag = true; break 'compute_need_tag;
}
}
let has_non_window_cb = !node_data.get_callbacks().is_empty()
&& !node_data.get_callbacks().iter().all(|cb| cb.event.is_window_callback());
if has_non_window_cb { need_tag = true; break 'compute_need_tag; }
if self.css_props.get_slice(node_idx).iter().any(|p|
p.state == azul_css::dynamic_selector::PseudoStateType::Normal
&& p.prop_type == CssPropertyType::Cursor
) || node_data.style.iter_inline_properties().any(|(p, _)|
p.get_type() == CssPropertyType::Cursor
) {
need_tag = true; break 'compute_need_tag;
}
if let Some(cc) = compact_cache.as_ref() {
let t1 = cc.tier1_enums[node_idx];
let ox = ((t1 >> OVERFLOW_X_SHIFT) & OVERFLOW_MASK) as u8;
let oy = ((t1 >> OVERFLOW_Y_SHIFT) & OVERFLOW_MASK) as u8;
if ox == 2 || ox == 3 || oy == 2 || oy == 3 {
need_tag = true; break 'compute_need_tag;
}
}
{
use crate::dom::NodeType;
let hier = node_hierarchy.as_container()[node_id];
let mut has_text = false;
if let Some(first_child) = hier.first_child_id(node_id) {
let mut child_id = Some(first_child);
while let Some(cid) = child_id {
if matches!(node_data_container[cid.index()].get_node_type(), NodeType::Text(_)) {
has_text = true; break;
}
child_id = node_hierarchy.as_container()[cid].next_sibling_id();
}
}
if has_text { need_tag = true; break 'compute_need_tag; }
}
break 'compute_need_tag;
}
if need_tag {
Some(TagIdToNodeIdMapping {
tag_id: TagId::from_crate_internal(TagId::unique()),
node_id: NodeHierarchyItemId::from_crate_internal(Some(node_id)),
tab_index: tab_index.into(),
})
} else {
None
}
})
.collect::<Vec<_>>();
tag_ids
}
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)] pub fn get_computed_css_style_string(
&self,
node_data: &NodeData,
node_id: &NodeId,
node_state: &StyledNodeState,
) -> String {
let mut s = String::new();
if let Some(p) = self.get_background_content(node_data, node_id, node_state) {
let _ = write!(s,"background: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_background_position(node_data, node_id, node_state) {
let _ = write!(s,"background-position: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_background_size(node_data, node_id, node_state) {
let _ = write!(s,"background-size: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_background_repeat(node_data, node_id, node_state) {
let _ = write!(s,"background-repeat: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_font_size(node_data, node_id, node_state) {
let _ = write!(s,"font-size: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_font_family(node_data, node_id, node_state) {
let _ = write!(s,"font-family: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_text_color(node_data, node_id, node_state) {
let _ = write!(s,"color: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_text_align(node_data, node_id, node_state) {
let _ = write!(s,"text-align: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_line_height(node_data, node_id, node_state) {
let _ = write!(s,"line-height: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_letter_spacing(node_data, node_id, node_state) {
let _ = write!(s,"letter-spacing: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_word_spacing(node_data, node_id, node_state) {
let _ = write!(s,"word-spacing: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_tab_size(node_data, node_id, node_state) {
let _ = write!(s,"tab-size: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_cursor(node_data, node_id, node_state) {
let _ = write!(s,"cursor: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_box_shadow_left(node_data, node_id, node_state) {
let _ = write!(s,
"-azul-box-shadow-left: {};",
p.get_css_value_fmt()
);
}
if let Some(p) = self.get_box_shadow_right(node_data, node_id, node_state) {
let _ = write!(s,
"-azul-box-shadow-right: {};",
p.get_css_value_fmt()
);
}
if let Some(p) = self.get_box_shadow_top(node_data, node_id, node_state) {
let _ = write!(s,"-azul-box-shadow-top: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_box_shadow_bottom(node_data, node_id, node_state) {
let _ = write!(s,
"-azul-box-shadow-bottom: {};",
p.get_css_value_fmt()
);
}
if let Some(p) = self.get_border_top_color(node_data, node_id, node_state) {
let _ = write!(s,"border-top-color: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_border_left_color(node_data, node_id, node_state) {
let _ = write!(s,"border-left-color: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_border_right_color(node_data, node_id, node_state) {
let _ = write!(s,"border-right-color: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_border_bottom_color(node_data, node_id, node_state) {
let _ = write!(s,"border-bottom-color: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_border_top_style(node_data, node_id, node_state) {
let _ = write!(s,"border-top-style: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_border_left_style(node_data, node_id, node_state) {
let _ = write!(s,"border-left-style: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_border_right_style(node_data, node_id, node_state) {
let _ = write!(s,"border-right-style: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_border_bottom_style(node_data, node_id, node_state) {
let _ = write!(s,"border-bottom-style: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_border_top_left_radius(node_data, node_id, node_state) {
let _ = write!(s,
"border-top-left-radius: {};",
p.get_css_value_fmt()
);
}
if let Some(p) = self.get_border_top_right_radius(node_data, node_id, node_state) {
let _ = write!(s,
"border-top-right-radius: {};",
p.get_css_value_fmt()
);
}
if let Some(p) = self.get_border_bottom_left_radius(node_data, node_id, node_state) {
let _ = write!(s,
"border-bottom-left-radius: {};",
p.get_css_value_fmt()
);
}
if let Some(p) = self.get_border_bottom_right_radius(node_data, node_id, node_state) {
let _ = write!(s,
"border-bottom-right-radius: {};",
p.get_css_value_fmt()
);
}
if let Some(p) = self.get_opacity(node_data, node_id, node_state) {
let _ = write!(s,"opacity: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_transform(node_data, node_id, node_state) {
let _ = write!(s,"transform: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_transform_origin(node_data, node_id, node_state) {
let _ = write!(s,"transform-origin: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_perspective_origin(node_data, node_id, node_state) {
let _ = write!(s,"perspective-origin: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_backface_visibility(node_data, node_id, node_state) {
let _ = write!(s,"backface-visibility: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_hyphens(node_data, node_id, node_state) {
let _ = write!(s,"hyphens: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_direction(node_data, node_id, node_state) {
let _ = write!(s,"direction: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_unicode_bidi(node_data, node_id, node_state) {
let _ = write!(s,"unicode-bidi: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_text_box_trim(node_data, node_id, node_state) {
let _ = write!(s,"text-box-trim: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_text_box_edge(node_data, node_id, node_state) {
let _ = write!(s,"text-box-edge: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_dominant_baseline(node_data, node_id, node_state) {
let _ = write!(s,"dominant-baseline: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_alignment_baseline(node_data, node_id, node_state) {
let _ = write!(s,"alignment-baseline: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_baseline_source(node_data, node_id, node_state) {
let _ = write!(s,"baseline-source: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_line_fit_edge(node_data, node_id, node_state) {
let _ = write!(s,"line-fit-edge: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_initial_letter_align(node_data, node_id, node_state) {
let _ = write!(s,"initial-letter-align: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_initial_letter_wrap(node_data, node_id, node_state) {
let _ = write!(s,"initial-letter-wrap: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_scrollbar_gutter(node_data, node_id, node_state) {
let _ = write!(s,"scrollbar-gutter: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_overflow_clip_margin(node_data, node_id, node_state) {
let _ = write!(s,"overflow-clip-margin: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_clip(node_data, node_id, node_state) {
let _ = write!(s,"clip: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_white_space(node_data, node_id, node_state) {
let _ = write!(s,"white-space: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_display(node_data, node_id, node_state) {
let _ = write!(s,"display: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_float(node_data, node_id, node_state) {
let _ = write!(s,"float: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_box_sizing(node_data, node_id, node_state) {
let _ = write!(s,"box-sizing: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_width(node_data, node_id, node_state) {
let _ = write!(s,"width: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_height(node_data, node_id, node_state) {
let _ = write!(s,"height: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_min_width(node_data, node_id, node_state) {
let _ = write!(s,"min-width: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_min_height(node_data, node_id, node_state) {
let _ = write!(s,"min-height: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_max_width(node_data, node_id, node_state) {
let _ = write!(s,"max-width: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_max_height(node_data, node_id, node_state) {
let _ = write!(s,"max-height: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_position(node_data, node_id, node_state) {
let _ = write!(s,"position: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_top(node_data, node_id, node_state) {
let _ = write!(s,"top: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_bottom(node_data, node_id, node_state) {
let _ = write!(s,"bottom: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_right(node_data, node_id, node_state) {
let _ = write!(s,"right: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_left(node_data, node_id, node_state) {
let _ = write!(s,"left: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_padding_top(node_data, node_id, node_state) {
let _ = write!(s,"padding-top: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_padding_bottom(node_data, node_id, node_state) {
let _ = write!(s,"padding-bottom: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_padding_left(node_data, node_id, node_state) {
let _ = write!(s,"padding-left: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_padding_right(node_data, node_id, node_state) {
let _ = write!(s,"padding-right: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_margin_top(node_data, node_id, node_state) {
let _ = write!(s,"margin-top: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_margin_bottom(node_data, node_id, node_state) {
let _ = write!(s,"margin-bottom: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_margin_left(node_data, node_id, node_state) {
let _ = write!(s,"margin-left: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_margin_right(node_data, node_id, node_state) {
let _ = write!(s,"margin-right: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_border_top_width(node_data, node_id, node_state) {
let _ = write!(s,"border-top-width: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_border_left_width(node_data, node_id, node_state) {
let _ = write!(s,"border-left-width: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_border_right_width(node_data, node_id, node_state) {
let _ = write!(s,"border-right-width: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_border_bottom_width(node_data, node_id, node_state) {
let _ = write!(s,"border-bottom-width: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_overflow_x(node_data, node_id, node_state) {
let _ = write!(s,"overflow-x: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_overflow_y(node_data, node_id, node_state) {
let _ = write!(s,"overflow-y: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_flex_direction(node_data, node_id, node_state) {
let _ = write!(s,"flex-direction: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_flex_wrap(node_data, node_id, node_state) {
let _ = write!(s,"flex-wrap: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_flex_grow(node_data, node_id, node_state) {
let _ = write!(s,"flex-grow: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_flex_shrink(node_data, node_id, node_state) {
let _ = write!(s,"flex-shrink: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_justify_content(node_data, node_id, node_state) {
let _ = write!(s,"justify-content: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_align_items(node_data, node_id, node_state) {
let _ = write!(s,"align-items: {};", p.get_css_value_fmt());
}
if let Some(p) = self.get_align_content(node_data, node_id, node_state) {
let _ = write!(s,"align-content: {};", p.get_css_value_fmt());
}
s
}
}
#[repr(C)]
#[derive(Debug, PartialEq, Clone)]
pub struct CssPropertyCachePtr {
pub ptr: ManuallyDrop<Box<CssPropertyCache>>,
pub run_destructor: bool,
}
impl CssPropertyCachePtr {
pub fn new(cache: CssPropertyCache) -> Self {
Self {
ptr: ManuallyDrop::new(Box::new(cache)),
run_destructor: true,
}
}
pub fn downcast_mut(&mut self) -> &mut CssPropertyCache {
&mut self.ptr
}
}
impl Drop for CssPropertyCachePtr {
fn drop(&mut self) {
if self.run_destructor {
self.run_destructor = false;
unsafe {
ManuallyDrop::drop(&mut self.ptr);
}
}
}
}
macro_rules! impl_get_prop {
($name:ident, $value_ty:ty, $variant:ident, $as_method:ident) => {
pub fn $name<'a>(
&'a self,
node_data: &'a NodeData,
node_id: &NodeId,
node_state: &StyledNodeState,
) -> Option<&'a $value_ty> {
self.get_property(node_data, node_id, node_state, &CssPropertyType::$variant)
.and_then(|p| p.$as_method())
}
};
}
impl CssPropertyCache {
#[must_use] pub fn empty(node_count: usize) -> Self {
Self {
node_count,
retained_author_css: Css::default(),
user_overridden_properties: Vec::new(),
cascaded_props: FlatVecVec::new(node_count),
css_props: FlatVecVec::new(node_count),
computed_values: Vec::new(),
compact_cache: None,
global_css_props: Vec::new(),
resolved_font_sizes_px: crate::sync::OnceLock::new(),
}
}
pub fn invalidate_resolved_font_sizes(&mut self) {
self.resolved_font_sizes_px = crate::sync::OnceLock::new();
}
pub fn append(&mut self, other: &mut Self) {
self.user_overridden_properties.append(&mut other.user_overridden_properties);
self.cascaded_props.extend_from(&mut other.cascaded_props);
self.css_props.extend_from(&mut other.css_props);
self.computed_values.append(&mut other.computed_values);
self.node_count += other.node_count;
self.resolved_font_sizes_px = crate::sync::OnceLock::new();
self.compact_cache = None;
}
pub fn is_horizontal_overflow_visible(
&self,
node_data: &NodeData,
node_id: &NodeId,
node_state: &StyledNodeState,
) -> bool {
self.get_overflow_x(node_data, node_id, node_state)
.and_then(|p| p.get_property_or_default())
.unwrap_or_default()
.is_overflow_visible()
}
pub fn is_vertical_overflow_visible(
&self,
node_data: &NodeData,
node_id: &NodeId,
node_state: &StyledNodeState,
) -> bool {
self.get_overflow_y(node_data, node_id, node_state)
.and_then(|p| p.get_property_or_default())
.unwrap_or_default()
.is_overflow_visible()
}
pub fn is_horizontal_overflow_hidden(
&self,
node_data: &NodeData,
node_id: &NodeId,
node_state: &StyledNodeState,
) -> bool {
self.get_overflow_x(node_data, node_id, node_state)
.and_then(|p| p.get_property_or_default())
.unwrap_or_default()
.is_overflow_hidden()
}
pub fn is_vertical_overflow_hidden(
&self,
node_data: &NodeData,
node_id: &NodeId,
node_state: &StyledNodeState,
) -> bool {
self.get_overflow_y(node_data, node_id, node_state)
.and_then(|p| p.get_property_or_default())
.unwrap_or_default()
.is_overflow_hidden()
}
pub fn get_text_color_or_default(
&self,
node_data: &NodeData,
node_id: &NodeId,
node_state: &StyledNodeState,
) -> StyleTextColor {
use azul_css::defaults::DEFAULT_TEXT_COLOR;
self.get_text_color(node_data, node_id, node_state)
.and_then(|fs| fs.get_property().copied())
.unwrap_or(DEFAULT_TEXT_COLOR)
}
pub fn get_font_id_or_default(
&self,
node_data: &NodeData,
node_id: &NodeId,
node_state: &StyledNodeState,
) -> StyleFontFamilyVec {
use azul_css::defaults::DEFAULT_FONT_ID;
let default_font_id = vec![StyleFontFamily::System(AzString::from_const_str(
DEFAULT_FONT_ID,
))]
.into();
let font_family_opt = self.get_font_family(node_data, node_id, node_state);
font_family_opt
.as_ref()
.and_then(|family| Some(family.get_property()?.clone()))
.unwrap_or(default_font_id)
}
pub fn get_font_size_or_default(
&self,
node_data: &NodeData,
node_id: &NodeId,
node_state: &StyledNodeState,
) -> StyleFontSize {
use azul_css::defaults::DEFAULT_FONT_SIZE;
self.get_font_size(node_data, node_id, node_state)
.and_then(|fs| fs.get_property().copied())
.unwrap_or(DEFAULT_FONT_SIZE)
}
pub fn has_border(
&self,
node_data: &NodeData,
node_id: &NodeId,
node_state: &StyledNodeState,
) -> bool {
self.get_border_left_width(node_data, node_id, node_state)
.is_some()
|| self
.get_border_right_width(node_data, node_id, node_state)
.is_some()
|| self
.get_border_top_width(node_data, node_id, node_state)
.is_some()
|| self
.get_border_bottom_width(node_data, node_id, node_state)
.is_some()
}
pub fn has_box_shadow(
&self,
node_data: &NodeData,
node_id: &NodeId,
node_state: &StyledNodeState,
) -> bool {
self.get_box_shadow_left(node_data, node_id, node_state)
.is_some()
|| self
.get_box_shadow_right(node_data, node_id, node_state)
.is_some()
|| self
.get_box_shadow_top(node_data, node_id, node_state)
.is_some()
|| self
.get_box_shadow_bottom(node_data, node_id, node_state)
.is_some()
}
pub fn get_property<'a>(
&'a self,
node_data: &'a NodeData,
node_id: &NodeId,
node_state: &StyledNodeState,
css_property_type: &CssPropertyType,
) -> Option<&'a CssProperty> {
#[cfg(feature = "std")]
{
static PROP_COUNT_ENABLED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
let enabled = *PROP_COUNT_ENABLED.get_or_init(crate::profile::cascade_enabled);
if enabled {
let _ = PROP_COUNTS.try_with(|c| {
*c.borrow_mut()
.entry(Self::css_prop_type_label(css_property_type))
.or_insert(0) += 1;
});
}
}
self.get_property_slow(node_data, node_id, node_state, css_property_type)
}
#[cfg(feature = "std")]
#[allow(clippy::trivially_copy_pass_by_ref)] fn css_prop_type_label(t: &CssPropertyType) -> &'static str {
use std::sync::{Mutex, OnceLock};
static TABLE: OnceLock<Mutex<std::collections::HashMap<CssPropertyType, &'static str>>> =
OnceLock::new();
let m = TABLE.get_or_init(|| Mutex::new(std::collections::HashMap::new()));
let mut g = m.lock().expect("AZ_PROP_COUNT label table poisoned");
if let Some(s) = g.get(t) {
return s;
}
let s: String = std::format!("{t:?}");
let leaked: &'static str = std::boxed::Box::leak(s.into_boxed_str());
g.insert(*t, leaked);
leaked
}
#[allow(clippy::trivially_copy_pass_by_ref)] #[allow(clippy::too_many_lines)] pub(crate) fn get_property_slow<'a>(
&'a self,
node_data: &'a NodeData,
node_id: &NodeId,
node_state: &StyledNodeState,
css_property_type: &CssPropertyType,
) -> Option<&'a CssProperty> {
use azul_css::dynamic_selector::{DynamicSelector, PseudoStateType};
fn matches_pseudo_state(
conds: &azul_css::dynamic_selector::DynamicSelectorVec,
state: PseudoStateType,
) -> bool {
let conditions = conds.as_slice();
if conditions.is_empty() {
state == PseudoStateType::Normal
} else {
conditions
.iter()
.all(|c| matches!(c, DynamicSelector::PseudoState(s) if *s == state))
}
}
if let Some(v) = self.user_overridden_properties.get(node_id.index()) {
if let Ok(idx) = v.binary_search_by_key(css_property_type, |(k, _)| *k) {
return Some(&v[idx].1);
}
}
if node_state.focused {
if let Some(p) = node_data.style.iter_inline_properties().find_map(|(prop, conds)| {
if matches_pseudo_state(conds,PseudoStateType::Focus)
&& prop.get_type() == *css_property_type
{
Some(prop)
} else {
None
}
}) {
return Some(p);
}
if let Some(p) = Self::find_in_stateful(
self.css_props.get_slice(node_id.index()),
PseudoStateType::Focus,
css_property_type,
) {
return Some(p);
}
if let Some(p) = Self::find_in_stateful(
self.cascaded_props.get_slice(node_id.index()),
PseudoStateType::Focus,
css_property_type,
) {
return Some(p);
}
}
if node_state.active {
if let Some(p) = node_data.style.iter_inline_properties().find_map(|(prop, conds)| {
if matches_pseudo_state(conds,PseudoStateType::Active)
&& prop.get_type() == *css_property_type
{
Some(prop)
} else {
None
}
}) {
return Some(p);
}
if let Some(p) = Self::find_in_stateful(
self.css_props.get_slice(node_id.index()),
PseudoStateType::Active,
css_property_type,
) {
return Some(p);
}
if let Some(p) = Self::find_in_stateful(
self.cascaded_props.get_slice(node_id.index()),
PseudoStateType::Active,
css_property_type,
) {
return Some(p);
}
}
if node_state.dragging {
if let Some(p) = node_data.style.iter_inline_properties().find_map(|(prop, conds)| {
if matches_pseudo_state(conds,PseudoStateType::Dragging)
&& prop.get_type() == *css_property_type
{
Some(prop)
} else {
None
}
}) {
return Some(p);
}
if let Some(p) = Self::find_in_stateful(
self.css_props.get_slice(node_id.index()),
PseudoStateType::Dragging,
css_property_type,
) {
return Some(p);
}
if let Some(p) = Self::find_in_stateful(
self.cascaded_props.get_slice(node_id.index()),
PseudoStateType::Dragging,
css_property_type,
) {
return Some(p);
}
}
if node_state.drag_over {
if let Some(p) = node_data.style.iter_inline_properties().find_map(|(prop, conds)| {
if matches_pseudo_state(conds,PseudoStateType::DragOver)
&& prop.get_type() == *css_property_type
{
Some(prop)
} else {
None
}
}) {
return Some(p);
}
if let Some(p) = Self::find_in_stateful(
self.css_props.get_slice(node_id.index()),
PseudoStateType::DragOver,
css_property_type,
) {
return Some(p);
}
if let Some(p) = Self::find_in_stateful(
self.cascaded_props.get_slice(node_id.index()),
PseudoStateType::DragOver,
css_property_type,
) {
return Some(p);
}
}
if node_state.hover {
if let Some(p) = node_data.style.iter_inline_properties().find_map(|(prop, conds)| {
if matches_pseudo_state(conds,PseudoStateType::Hover)
&& prop.get_type() == *css_property_type
{
Some(prop)
} else {
None
}
}) {
return Some(p);
}
if let Some(p) = Self::find_in_stateful(
self.css_props.get_slice(node_id.index()),
PseudoStateType::Hover,
css_property_type,
) {
return Some(p);
}
if let Some(p) = Self::find_in_stateful(
self.cascaded_props.get_slice(node_id.index()),
PseudoStateType::Hover,
css_property_type,
) {
return Some(p);
}
}
if let Some(p) = node_data.style.iter_inline_properties().find_map(|(prop, conds)| {
if matches_pseudo_state(conds, PseudoStateType::Normal)
&& prop.get_type() == *css_property_type
{
Some(prop)
} else {
None
}
}) {
return Some(p);
}
if let Some(p) = Self::find_in_stateful(
self.css_props.get_slice(node_id.index()),
PseudoStateType::Normal,
css_property_type,
) {
return Some(p);
}
if let Some(p) = self.global_css_props.iter().find(|p| p.get_type() == *css_property_type) {
return Some(p);
}
if let Some(p) = Self::find_in_stateful(
self.cascaded_props.get_slice(node_id.index()),
PseudoStateType::Normal,
css_property_type,
) {
return Some(p);
}
if css_property_type.is_inheritable() {
if let Some(vec) = self.computed_values.get(node_id.index()) {
if let Ok(idx) = vec.binary_search_by_key(css_property_type, |(k, _)| *k) {
return Some(&vec[idx].1.property);
}
}
}
crate::ua_css::get_ua_property(&node_data.node_type, *css_property_type)
}
#[allow(clippy::trivially_copy_pass_by_ref)] pub(crate) fn get_property_with_context<'a>(
&'a self,
node_data: &'a NodeData,
node_id: &NodeId,
context: &DynamicSelectorContext,
css_property_type: &CssPropertyType,
) -> Option<&'a CssProperty> {
if let Some(v) = self.user_overridden_properties.get(node_id.index()) {
if let Ok(idx) = v.binary_search_by_key(css_property_type, |(k, _)| *k) {
return Some(&v[idx].1);
}
}
let mut last_inline = None;
for (prop, conds) in node_data.style.iter_inline_properties() {
let conditions_match = conds.as_slice().iter().all(|c| c.matches(context));
if prop.get_type() == *css_property_type && conditions_match {
last_inline = Some(prop);
}
}
if let Some(prop) = last_inline {
return Some(prop);
}
let legacy_state = StyledNodeState::from_pseudo_state_flags(&context.pseudo_state);
if let Some(p) = self.get_property(node_data, node_id, &legacy_state, css_property_type) {
return Some(p);
}
None
}
pub(crate) fn check_properties_changed(
node_data: &NodeData,
old_context: &DynamicSelectorContext,
new_context: &DynamicSelectorContext,
) -> bool {
for (_prop, conds) in node_data.style.iter_inline_properties() {
let was_active = conds.as_slice().iter().all(|c| c.matches(old_context));
let is_active = conds.as_slice().iter().all(|c| c.matches(new_context));
if was_active != is_active {
return true;
}
}
false
}
pub(crate) fn check_layout_properties_changed(
node_data: &NodeData,
old_context: &DynamicSelectorContext,
new_context: &DynamicSelectorContext,
) -> bool {
for (prop, conds) in node_data.style.iter_inline_properties() {
if !prop.get_type().can_trigger_relayout() {
continue;
}
let was_active = conds.as_slice().iter().all(|c| c.matches(old_context));
let is_active = conds.as_slice().iter().all(|c| c.matches(new_context));
if was_active != is_active {
return true;
}
}
false
}
impl_get_prop!(get_background_content, StyleBackgroundContentVecValue, BackgroundContent, as_background_content);
impl_get_prop!(get_hyphens, StyleHyphensValue, Hyphens, as_hyphens);
impl_get_prop!(get_word_break, StyleWordBreakValue, WordBreak, as_word_break);
impl_get_prop!(get_overflow_wrap, StyleOverflowWrapValue, OverflowWrap, as_overflow_wrap);
impl_get_prop!(get_line_break, StyleLineBreakValue, LineBreak, as_line_break);
impl_get_prop!(get_text_align_last, StyleTextAlignLastValue, TextAlignLast, as_text_align_last);
impl_get_prop!(get_text_transform, StyleTextTransformValue, TextTransform, as_text_transform);
impl_get_prop!(get_object_fit, StyleObjectFitValue, ObjectFit, as_object_fit);
impl_get_prop!(get_text_overflow, StyleTextOverflowValue, TextOverflow, as_text_overflow);
impl_get_prop!(get_text_orientation, StyleTextOrientationValue, TextOrientation, as_text_orientation);
impl_get_prop!(get_object_position, StyleObjectPositionValue, ObjectPosition, as_object_position);
impl_get_prop!(get_aspect_ratio, StyleAspectRatioValue, AspectRatio, as_aspect_ratio);
impl_get_prop!(get_direction, StyleDirectionValue, Direction, as_direction);
impl_get_prop!(get_unicode_bidi, StyleUnicodeBidiValue, UnicodeBidi, as_unicode_bidi);
impl_get_prop!(get_text_box_trim, StyleTextBoxTrimValue, TextBoxTrim, as_text_box_trim);
impl_get_prop!(get_text_box_edge, StyleTextBoxEdgeValue, TextBoxEdge, as_text_box_edge);
impl_get_prop!(get_dominant_baseline, StyleDominantBaselineValue, DominantBaseline, as_dominant_baseline);
impl_get_prop!(get_alignment_baseline, StyleAlignmentBaselineValue, AlignmentBaseline, as_alignment_baseline);
impl_get_prop!(get_baseline_source, StyleBaselineSourceValue, BaselineSource, as_baseline_source);
impl_get_prop!(get_line_fit_edge, StyleLineFitEdgeValue, LineFitEdge, as_line_fit_edge);
impl_get_prop!(get_initial_letter_align, StyleInitialLetterAlignValue, InitialLetterAlign, as_initial_letter_align);
impl_get_prop!(get_initial_letter_wrap, StyleInitialLetterWrapValue, InitialLetterWrap, as_initial_letter_wrap);
impl_get_prop!(get_scrollbar_gutter, StyleScrollbarGutterValue, ScrollbarGutter, as_scrollbar_gutter);
impl_get_prop!(get_overflow_clip_margin, StyleOverflowClipMarginValue, OverflowClipMargin, as_overflow_clip_margin);
impl_get_prop!(get_clip, StyleClipRectValue, Clip, as_clip);
impl_get_prop!(get_white_space, StyleWhiteSpaceValue, WhiteSpace, as_white_space);
impl_get_prop!(get_background_position, StyleBackgroundPositionVecValue, BackgroundPosition, as_background_position);
impl_get_prop!(get_background_size, StyleBackgroundSizeVecValue, BackgroundSize, as_background_size);
impl_get_prop!(get_background_repeat, StyleBackgroundRepeatVecValue, BackgroundRepeat, as_background_repeat);
impl_get_prop!(get_font_size, StyleFontSizeValue, FontSize, as_font_size);
impl_get_prop!(get_font_family, StyleFontFamilyVecValue, FontFamily, as_font_family);
impl_get_prop!(get_font_weight, StyleFontWeightValue, FontWeight, as_font_weight);
impl_get_prop!(get_font_style, StyleFontStyleValue, FontStyle, as_font_style);
impl_get_prop!(get_text_color, StyleTextColorValue, TextColor, as_text_color);
impl_get_prop!(get_text_indent, StyleTextIndentValue, TextIndent, as_text_indent);
impl_get_prop!(get_initial_letter, StyleInitialLetterValue, InitialLetter, as_initial_letter);
impl_get_prop!(get_line_clamp, StyleLineClampValue, LineClamp, as_line_clamp);
impl_get_prop!(get_hanging_punctuation, StyleHangingPunctuationValue, HangingPunctuation, as_hanging_punctuation);
impl_get_prop!(get_text_combine_upright, StyleTextCombineUprightValue, TextCombineUpright, as_text_combine_upright);
impl_get_prop!(get_exclusion_margin, StyleExclusionMarginValue, ExclusionMargin, as_exclusion_margin);
impl_get_prop!(get_hyphenation_language, StyleHyphenationLanguageValue, HyphenationLanguage, as_hyphenation_language);
impl_get_prop!(get_caret_color, CaretColorValue, CaretColor, as_caret_color);
impl_get_prop!(get_caret_width, CaretWidthValue, CaretWidth, as_caret_width);
impl_get_prop!(get_caret_animation_duration, CaretAnimationDurationValue, CaretAnimationDuration, as_caret_animation_duration);
impl_get_prop!(get_selection_background_color, SelectionBackgroundColorValue, SelectionBackgroundColor, as_selection_background_color);
impl_get_prop!(get_selection_color, SelectionColorValue, SelectionColor, as_selection_color);
impl_get_prop!(get_selection_radius, SelectionRadiusValue, SelectionRadius, as_selection_radius);
impl_get_prop!(get_text_justify, LayoutTextJustifyValue, TextJustify, as_text_justify);
impl_get_prop!(get_z_index, LayoutZIndexValue, ZIndex, as_z_index);
impl_get_prop!(get_flex_basis, LayoutFlexBasisValue, FlexBasis, as_flex_basis);
impl_get_prop!(get_column_gap, LayoutColumnGapValue, ColumnGap, as_column_gap);
impl_get_prop!(get_row_gap, LayoutRowGapValue, RowGap, as_row_gap);
impl_get_prop!(get_grid_template_columns, LayoutGridTemplateColumnsValue, GridTemplateColumns, as_grid_template_columns);
impl_get_prop!(get_grid_template_rows, LayoutGridTemplateRowsValue, GridTemplateRows, as_grid_template_rows);
impl_get_prop!(get_grid_auto_columns, LayoutGridAutoColumnsValue, GridAutoColumns, as_grid_auto_columns);
impl_get_prop!(get_grid_auto_rows, LayoutGridAutoRowsValue, GridAutoRows, as_grid_auto_rows);
impl_get_prop!(get_grid_column, LayoutGridColumnValue, GridColumn, as_grid_column);
impl_get_prop!(get_grid_row, LayoutGridRowValue, GridRow, as_grid_row);
impl_get_prop!(get_grid_auto_flow, LayoutGridAutoFlowValue, GridAutoFlow, as_grid_auto_flow);
impl_get_prop!(get_justify_self, LayoutJustifySelfValue, JustifySelf, as_justify_self);
impl_get_prop!(get_justify_items, LayoutJustifyItemsValue, JustifyItems, as_justify_items);
impl_get_prop!(get_gap, LayoutGapValue, Gap, as_gap);
#[allow(clippy::trivially_copy_pass_by_ref)] pub(crate) fn get_grid_gap<'a>(
&'a self,
node_data: &'a NodeData,
node_id: &NodeId,
node_state: &StyledNodeState,
) -> Option<&'a LayoutGapValue> {
self.get_property(node_data, node_id, node_state, &CssPropertyType::GridGap)
.and_then(|p| p.as_grid_gap())
}
impl_get_prop!(get_align_self, LayoutAlignSelfValue, AlignSelf, as_align_self);
impl_get_prop!(get_font, StyleFontValue, Font, as_font);
impl_get_prop!(get_writing_mode, LayoutWritingModeValue, WritingMode, as_writing_mode);
impl_get_prop!(get_clear, LayoutClearValue, Clear, as_clear);
impl_get_prop!(get_shape_outside, ShapeOutsideValue, ShapeOutside, as_shape_outside);
impl_get_prop!(get_shape_inside, ShapeInsideValue, ShapeInside, as_shape_inside);
impl_get_prop!(get_clip_path, ClipPathValue, ClipPath, as_clip_path);
pub fn get_scrollbar_track<'a>(
&'a self,
node_data: &'a NodeData,
node_id: &NodeId,
node_state: &StyledNodeState,
) -> Option<&'a StyleBackgroundContentValue> {
self.get_property(node_data, node_id, node_state, &CssPropertyType::ScrollbarTrack)
.and_then(|p| p.as_scrollbar_track())
}
pub fn get_scrollbar_thumb<'a>(
&'a self,
node_data: &'a NodeData,
node_id: &NodeId,
node_state: &StyledNodeState,
) -> Option<&'a StyleBackgroundContentValue> {
self.get_property(node_data, node_id, node_state, &CssPropertyType::ScrollbarThumb)
.and_then(|p| p.as_scrollbar_thumb())
}
pub fn get_scrollbar_button<'a>(
&'a self,
node_data: &'a NodeData,
node_id: &NodeId,
node_state: &StyledNodeState,
) -> Option<&'a StyleBackgroundContentValue> {
self.get_property(node_data, node_id, node_state, &CssPropertyType::ScrollbarButton)
.and_then(|p| p.as_scrollbar_button())
}
pub fn get_scrollbar_corner<'a>(
&'a self,
node_data: &'a NodeData,
node_id: &NodeId,
node_state: &StyledNodeState,
) -> Option<&'a StyleBackgroundContentValue> {
self.get_property(node_data, node_id, node_state, &CssPropertyType::ScrollbarCorner)
.and_then(|p| p.as_scrollbar_corner())
}
pub fn get_scrollbar_resizer<'a>(
&'a self,
node_data: &'a NodeData,
node_id: &NodeId,
node_state: &StyledNodeState,
) -> Option<&'a StyleBackgroundContentValue> {
self.get_property(node_data, node_id, node_state, &CssPropertyType::ScrollbarResizer)
.and_then(|p| p.as_scrollbar_resizer())
}
impl_get_prop!(get_scrollbar_width, LayoutScrollbarWidthValue, ScrollbarWidth, as_scrollbar_width);
impl_get_prop!(get_scrollbar_color, StyleScrollbarColorValue, ScrollbarColor, as_scrollbar_color);
impl_get_prop!(get_scrollbar_visibility, ScrollbarVisibilityModeValue, ScrollbarVisibility, as_scrollbar_visibility);
impl_get_prop!(get_scrollbar_fade_delay, ScrollbarFadeDelayValue, ScrollbarFadeDelay, as_scrollbar_fade_delay);
impl_get_prop!(get_scrollbar_fade_duration, ScrollbarFadeDurationValue, ScrollbarFadeDuration, as_scrollbar_fade_duration);
impl_get_prop!(get_visibility, StyleVisibilityValue, Visibility, as_visibility);
impl_get_prop!(get_break_before, PageBreakValue, BreakBefore, as_break_before);
impl_get_prop!(get_break_after, PageBreakValue, BreakAfter, as_break_after);
impl_get_prop!(get_break_inside, BreakInsideValue, BreakInside, as_break_inside);
impl_get_prop!(get_orphans, OrphansValue, Orphans, as_orphans);
impl_get_prop!(get_widows, WidowsValue, Widows, as_widows);
impl_get_prop!(get_box_decoration_break, BoxDecorationBreakValue, BoxDecorationBreak, as_box_decoration_break);
impl_get_prop!(get_column_count, ColumnCountValue, ColumnCount, as_column_count);
impl_get_prop!(get_column_width, ColumnWidthValue, ColumnWidth, as_column_width);
impl_get_prop!(get_column_span, ColumnSpanValue, ColumnSpan, as_column_span);
impl_get_prop!(get_column_fill, ColumnFillValue, ColumnFill, as_column_fill);
impl_get_prop!(get_column_rule_width, ColumnRuleWidthValue, ColumnRuleWidth, as_column_rule_width);
impl_get_prop!(get_column_rule_style, ColumnRuleStyleValue, ColumnRuleStyle, as_column_rule_style);
impl_get_prop!(get_column_rule_color, ColumnRuleColorValue, ColumnRuleColor, as_column_rule_color);
impl_get_prop!(get_flow_into, FlowIntoValue, FlowInto, as_flow_into);
impl_get_prop!(get_flow_from, FlowFromValue, FlowFrom, as_flow_from);
impl_get_prop!(get_shape_margin, ShapeMarginValue, ShapeMargin, as_shape_margin);
impl_get_prop!(get_shape_image_threshold, ShapeImageThresholdValue, ShapeImageThreshold, as_shape_image_threshold);
impl_get_prop!(get_content, ContentValue, Content, as_content);
impl_get_prop!(get_counter_reset, CounterResetValue, CounterReset, as_counter_reset);
impl_get_prop!(get_counter_increment, CounterIncrementValue, CounterIncrement, as_counter_increment);
impl_get_prop!(get_string_set, StringSetValue, StringSet, as_string_set);
impl_get_prop!(get_text_align, StyleTextAlignValue, TextAlign, as_text_align);
impl_get_prop!(get_user_select, StyleUserSelectValue, UserSelect, as_user_select);
impl_get_prop!(get_text_decoration, StyleTextDecorationValue, TextDecoration, as_text_decoration);
impl_get_prop!(get_vertical_align, StyleVerticalAlignValue, VerticalAlign, as_vertical_align);
impl_get_prop!(get_line_height, StyleLineHeightValue, LineHeight, as_line_height);
impl_get_prop!(get_letter_spacing, StyleLetterSpacingValue, LetterSpacing, as_letter_spacing);
impl_get_prop!(get_word_spacing, StyleWordSpacingValue, WordSpacing, as_word_spacing);
impl_get_prop!(get_tab_size, StyleTabSizeValue, TabSize, as_tab_size);
impl_get_prop!(get_cursor, StyleCursorValue, Cursor, as_cursor);
impl_get_prop!(get_box_shadow_left, StyleBoxShadowValue, BoxShadowLeft, as_box_shadow_left);
impl_get_prop!(get_box_shadow_right, StyleBoxShadowValue, BoxShadowRight, as_box_shadow_right);
impl_get_prop!(get_box_shadow_top, StyleBoxShadowValue, BoxShadowTop, as_box_shadow_top);
impl_get_prop!(get_box_shadow_bottom, StyleBoxShadowValue, BoxShadowBottom, as_box_shadow_bottom);
impl_get_prop!(get_border_top_color, StyleBorderTopColorValue, BorderTopColor, as_border_top_color);
impl_get_prop!(get_border_left_color, StyleBorderLeftColorValue, BorderLeftColor, as_border_left_color);
impl_get_prop!(get_border_right_color, StyleBorderRightColorValue, BorderRightColor, as_border_right_color);
impl_get_prop!(get_border_bottom_color, StyleBorderBottomColorValue, BorderBottomColor, as_border_bottom_color);
impl_get_prop!(get_border_top_style, StyleBorderTopStyleValue, BorderTopStyle, as_border_top_style);
impl_get_prop!(get_border_left_style, StyleBorderLeftStyleValue, BorderLeftStyle, as_border_left_style);
impl_get_prop!(get_border_right_style, StyleBorderRightStyleValue, BorderRightStyle, as_border_right_style);
impl_get_prop!(get_border_bottom_style, StyleBorderBottomStyleValue, BorderBottomStyle, as_border_bottom_style);
impl_get_prop!(get_border_top_left_radius, StyleBorderTopLeftRadiusValue, BorderTopLeftRadius, as_border_top_left_radius);
impl_get_prop!(get_border_top_right_radius, StyleBorderTopRightRadiusValue, BorderTopRightRadius, as_border_top_right_radius);
impl_get_prop!(get_border_bottom_left_radius, StyleBorderBottomLeftRadiusValue, BorderBottomLeftRadius, as_border_bottom_left_radius);
impl_get_prop!(get_border_bottom_right_radius, StyleBorderBottomRightRadiusValue, BorderBottomRightRadius, as_border_bottom_right_radius);
impl_get_prop!(get_opacity, StyleOpacityValue, Opacity, as_opacity);
impl_get_prop!(get_transform, StyleTransformVecValue, Transform, as_transform);
impl_get_prop!(get_transform_origin, StyleTransformOriginValue, TransformOrigin, as_transform_origin);
impl_get_prop!(get_perspective_origin, StylePerspectiveOriginValue, PerspectiveOrigin, as_perspective_origin);
impl_get_prop!(get_backface_visibility, StyleBackfaceVisibilityValue, BackfaceVisibility, as_backface_visibility);
impl_get_prop!(get_display, LayoutDisplayValue, Display, as_display);
impl_get_prop!(get_float, LayoutFloatValue, Float, as_float);
impl_get_prop!(get_box_sizing, LayoutBoxSizingValue, BoxSizing, as_box_sizing);
impl_get_prop!(get_width, LayoutWidthValue, Width, as_width);
impl_get_prop!(get_height, LayoutHeightValue, Height, as_height);
impl_get_prop!(get_min_width, LayoutMinWidthValue, MinWidth, as_min_width);
impl_get_prop!(get_min_height, LayoutMinHeightValue, MinHeight, as_min_height);
impl_get_prop!(get_max_width, LayoutMaxWidthValue, MaxWidth, as_max_width);
impl_get_prop!(get_max_height, LayoutMaxHeightValue, MaxHeight, as_max_height);
impl_get_prop!(get_position, LayoutPositionValue, Position, as_position);
impl_get_prop!(get_top, LayoutTopValue, Top, as_top);
impl_get_prop!(get_bottom, LayoutInsetBottomValue, Bottom, as_bottom);
impl_get_prop!(get_right, LayoutRightValue, Right, as_right);
impl_get_prop!(get_left, LayoutLeftValue, Left, as_left);
impl_get_prop!(get_padding_top, LayoutPaddingTopValue, PaddingTop, as_padding_top);
impl_get_prop!(get_padding_bottom, LayoutPaddingBottomValue, PaddingBottom, as_padding_bottom);
impl_get_prop!(get_padding_left, LayoutPaddingLeftValue, PaddingLeft, as_padding_left);
impl_get_prop!(get_padding_right, LayoutPaddingRightValue, PaddingRight, as_padding_right);
impl_get_prop!(get_margin_top, LayoutMarginTopValue, MarginTop, as_margin_top);
impl_get_prop!(get_margin_bottom, LayoutMarginBottomValue, MarginBottom, as_margin_bottom);
impl_get_prop!(get_margin_left, LayoutMarginLeftValue, MarginLeft, as_margin_left);
impl_get_prop!(get_margin_right, LayoutMarginRightValue, MarginRight, as_margin_right);
impl_get_prop!(get_border_top_width, LayoutBorderTopWidthValue, BorderTopWidth, as_border_top_width);
impl_get_prop!(get_border_left_width, LayoutBorderLeftWidthValue, BorderLeftWidth, as_border_left_width);
impl_get_prop!(get_border_right_width, LayoutBorderRightWidthValue, BorderRightWidth, as_border_right_width);
impl_get_prop!(get_border_bottom_width, LayoutBorderBottomWidthValue, BorderBottomWidth, as_border_bottom_width);
impl_get_prop!(get_overflow_x, LayoutOverflowValue, OverflowX, as_overflow_x);
impl_get_prop!(get_overflow_y, LayoutOverflowValue, OverflowY, as_overflow_y);
impl_get_prop!(get_overflow_block, LayoutOverflowValue, OverflowBlock, as_overflow_block);
impl_get_prop!(get_overflow_inline, LayoutOverflowValue, OverflowInline, as_overflow_inline);
impl_get_prop!(get_flex_direction, LayoutFlexDirectionValue, FlexDirection, as_flex_direction);
impl_get_prop!(get_flex_wrap, LayoutFlexWrapValue, FlexWrap, as_flex_wrap);
impl_get_prop!(get_flex_grow, LayoutFlexGrowValue, FlexGrow, as_flex_grow);
impl_get_prop!(get_flex_shrink, LayoutFlexShrinkValue, FlexShrink, as_flex_shrink);
impl_get_prop!(get_justify_content, LayoutJustifyContentValue, JustifyContent, as_justify_content);
impl_get_prop!(get_align_items, LayoutAlignItemsValue, AlignItems, as_align_items);
impl_get_prop!(get_align_content, LayoutAlignContentValue, AlignContent, as_align_content);
impl_get_prop!(get_mix_blend_mode, StyleMixBlendModeValue, MixBlendMode, as_mix_blend_mode);
impl_get_prop!(get_filter, StyleFilterVecValue, Filter, as_filter);
impl_get_prop!(get_backdrop_filter, StyleFilterVecValue, BackdropFilter, as_backdrop_filter);
impl_get_prop!(get_text_shadow, StyleBoxShadowValue, TextShadow, as_text_shadow);
impl_get_prop!(get_list_style_type, StyleListStyleTypeValue, ListStyleType, as_list_style_type);
impl_get_prop!(get_list_style_position, StyleListStylePositionValue, ListStylePosition, as_list_style_position);
impl_get_prop!(get_table_layout, LayoutTableLayoutValue, TableLayout, as_table_layout);
impl_get_prop!(get_border_collapse, StyleBorderCollapseValue, BorderCollapse, as_border_collapse);
impl_get_prop!(get_border_spacing, LayoutBorderSpacingValue, BorderSpacing, as_border_spacing);
impl_get_prop!(get_caption_side, StyleCaptionSideValue, CaptionSide, as_caption_side);
impl_get_prop!(get_empty_cells, StyleEmptyCellsValue, EmptyCells, as_empty_cells);
pub fn calc_width(
&self,
node_data: &NodeData,
node_id: &NodeId,
styled_node_state: &StyledNodeState,
reference_width: f32,
) -> f32 {
self.get_width(node_data, node_id, styled_node_state)
.and_then(|w| match w.get_property()? {
LayoutWidth::Px(px) => Some(px.to_pixels_internal(
reference_width,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
)),
_ => Some(0.0), })
.unwrap_or(0.0)
}
pub fn calc_min_width(
&self,
node_data: &NodeData,
node_id: &NodeId,
styled_node_state: &StyledNodeState,
reference_width: f32,
) -> f32 {
self.get_min_width(node_data, node_id, styled_node_state)
.and_then(|w| {
Some(w.get_property()?.inner.to_pixels_internal(
reference_width,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
))
})
.unwrap_or(0.0)
}
pub fn calc_max_width(
&self,
node_data: &NodeData,
node_id: &NodeId,
styled_node_state: &StyledNodeState,
reference_width: f32,
) -> Option<f32> {
self.get_max_width(node_data, node_id, styled_node_state)
.and_then(|w| {
Some(w.get_property()?.inner.to_pixels_internal(
reference_width,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
))
})
}
pub fn calc_height(
&self,
node_data: &NodeData,
node_id: &NodeId,
styled_node_state: &StyledNodeState,
reference_height: f32,
) -> f32 {
self.get_height(node_data, node_id, styled_node_state)
.and_then(|h| match h.get_property()? {
LayoutHeight::Px(px) => Some(px.to_pixels_internal(
reference_height,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
)),
_ => Some(0.0), })
.unwrap_or(0.0)
}
pub fn calc_min_height(
&self,
node_data: &NodeData,
node_id: &NodeId,
styled_node_state: &StyledNodeState,
reference_height: f32,
) -> f32 {
self.get_min_height(node_data, node_id, styled_node_state)
.and_then(|h| {
Some(h.get_property()?.inner.to_pixels_internal(
reference_height,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
))
})
.unwrap_or(0.0)
}
pub fn calc_max_height(
&self,
node_data: &NodeData,
node_id: &NodeId,
styled_node_state: &StyledNodeState,
reference_height: f32,
) -> Option<f32> {
self.get_max_height(node_data, node_id, styled_node_state)
.and_then(|h| {
Some(h.get_property()?.inner.to_pixels_internal(
reference_height,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
))
})
}
pub fn calc_left(
&self,
node_data: &NodeData,
node_id: &NodeId,
styled_node_state: &StyledNodeState,
reference_width: f32,
) -> Option<f32> {
self.get_left(node_data, node_id, styled_node_state)
.and_then(|l| {
Some(l.get_property()?.inner.to_pixels_internal(
reference_width,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
))
})
}
pub fn calc_right(
&self,
node_data: &NodeData,
node_id: &NodeId,
styled_node_state: &StyledNodeState,
reference_width: f32,
) -> Option<f32> {
self.get_right(node_data, node_id, styled_node_state)
.and_then(|r| {
Some(r.get_property()?.inner.to_pixels_internal(
reference_width,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
))
})
}
pub fn calc_top(
&self,
node_data: &NodeData,
node_id: &NodeId,
styled_node_state: &StyledNodeState,
reference_height: f32,
) -> Option<f32> {
self.get_top(node_data, node_id, styled_node_state)
.and_then(|t| {
Some(t.get_property()?.inner.to_pixels_internal(
reference_height,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
))
})
}
pub fn calc_bottom(
&self,
node_data: &NodeData,
node_id: &NodeId,
styled_node_state: &StyledNodeState,
reference_height: f32,
) -> Option<f32> {
self.get_bottom(node_data, node_id, styled_node_state)
.and_then(|b| {
Some(b.get_property()?.inner.to_pixels_internal(
reference_height,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
))
})
}
pub fn calc_border_left_width(
&self,
node_data: &NodeData,
node_id: &NodeId,
styled_node_state: &StyledNodeState,
reference_width: f32,
) -> f32 {
self.get_border_left_width(node_data, node_id, styled_node_state)
.and_then(|b| {
Some(b.get_property()?.inner.to_pixels_internal(
reference_width,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
))
})
.unwrap_or(0.0)
}
pub fn calc_border_right_width(
&self,
node_data: &NodeData,
node_id: &NodeId,
styled_node_state: &StyledNodeState,
reference_width: f32,
) -> f32 {
self.get_border_right_width(node_data, node_id, styled_node_state)
.and_then(|b| {
Some(b.get_property()?.inner.to_pixels_internal(
reference_width,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
))
})
.unwrap_or(0.0)
}
pub fn calc_border_top_width(
&self,
node_data: &NodeData,
node_id: &NodeId,
styled_node_state: &StyledNodeState,
reference_height: f32,
) -> f32 {
self.get_border_top_width(node_data, node_id, styled_node_state)
.and_then(|b| {
Some(b.get_property()?.inner.to_pixels_internal(
reference_height,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
))
})
.unwrap_or(0.0)
}
pub fn calc_border_bottom_width(
&self,
node_data: &NodeData,
node_id: &NodeId,
styled_node_state: &StyledNodeState,
reference_height: f32,
) -> f32 {
self.get_border_bottom_width(node_data, node_id, styled_node_state)
.and_then(|b| {
Some(b.get_property()?.inner.to_pixels_internal(
reference_height,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
))
})
.unwrap_or(0.0)
}
pub fn calc_padding_left(
&self,
node_data: &NodeData,
node_id: &NodeId,
styled_node_state: &StyledNodeState,
reference_width: f32,
) -> f32 {
self.get_padding_left(node_data, node_id, styled_node_state)
.and_then(|p| {
Some(p.get_property()?.inner.to_pixels_internal(
reference_width,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
))
})
.unwrap_or(0.0)
}
pub fn calc_padding_right(
&self,
node_data: &NodeData,
node_id: &NodeId,
styled_node_state: &StyledNodeState,
reference_width: f32,
) -> f32 {
self.get_padding_right(node_data, node_id, styled_node_state)
.and_then(|p| {
Some(p.get_property()?.inner.to_pixels_internal(
reference_width,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
))
})
.unwrap_or(0.0)
}
pub fn calc_padding_top(
&self,
node_data: &NodeData,
node_id: &NodeId,
styled_node_state: &StyledNodeState,
reference_height: f32,
) -> f32 {
self.get_padding_top(node_data, node_id, styled_node_state)
.and_then(|p| {
Some(p.get_property()?.inner.to_pixels_internal(
reference_height,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
))
})
.unwrap_or(0.0)
}
pub fn calc_padding_bottom(
&self,
node_data: &NodeData,
node_id: &NodeId,
styled_node_state: &StyledNodeState,
reference_height: f32,
) -> f32 {
self.get_padding_bottom(node_data, node_id, styled_node_state)
.and_then(|p| {
Some(p.get_property()?.inner.to_pixels_internal(
reference_height,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
))
})
.unwrap_or(0.0)
}
pub fn calc_margin_left(
&self,
node_data: &NodeData,
node_id: &NodeId,
styled_node_state: &StyledNodeState,
reference_width: f32,
) -> f32 {
self.get_margin_left(node_data, node_id, styled_node_state)
.and_then(|m| {
Some(m.get_property()?.inner.to_pixels_internal(
reference_width,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
))
})
.unwrap_or(0.0)
}
pub fn calc_margin_right(
&self,
node_data: &NodeData,
node_id: &NodeId,
styled_node_state: &StyledNodeState,
reference_width: f32,
) -> f32 {
self.get_margin_right(node_data, node_id, styled_node_state)
.and_then(|m| {
Some(m.get_property()?.inner.to_pixels_internal(
reference_width,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
))
})
.unwrap_or(0.0)
}
pub fn calc_margin_top(
&self,
node_data: &NodeData,
node_id: &NodeId,
styled_node_state: &StyledNodeState,
reference_height: f32,
) -> f32 {
self.get_margin_top(node_data, node_id, styled_node_state)
.and_then(|m| {
Some(m.get_property()?.inner.to_pixels_internal(
reference_height,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
))
})
.unwrap_or(0.0)
}
pub fn calc_margin_bottom(
&self,
node_data: &NodeData,
node_id: &NodeId,
styled_node_state: &StyledNodeState,
reference_height: f32,
) -> f32 {
self.get_margin_bottom(node_data, node_id, styled_node_state)
.and_then(|m| {
Some(m.get_property()?.inner.to_pixels_internal(
reference_height,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
))
})
.unwrap_or(0.0)
}
#[allow(clippy::too_many_lines)] fn resolve_property_dependency(
target_property: &CssProperty,
reference_property: &CssProperty,
) -> Option<CssProperty> {
#[allow(clippy::wildcard_imports)]
use azul_css::{
css::CssPropertyValue,
props::{
basic::{font::StyleFontSize, length::SizeMetric, pixel::PixelValue},
layout::*,
style::{SelectionRadius, StyleLetterSpacing, StyleWordSpacing},
},
};
let get_pixel_value = |prop: &CssProperty| -> Option<PixelValue> {
match prop {
CssProperty::FontSize(val) => val.get_property().map(|v| v.inner),
CssProperty::LetterSpacing(val) => val.get_property().map(|v| v.inner),
CssProperty::WordSpacing(val) => val.get_property().map(|v| v.inner),
CssProperty::PaddingLeft(val) => val.get_property().map(|v| v.inner),
CssProperty::PaddingRight(val) => val.get_property().map(|v| v.inner),
CssProperty::PaddingTop(val) => val.get_property().map(|v| v.inner),
CssProperty::PaddingBottom(val) => val.get_property().map(|v| v.inner),
CssProperty::MarginLeft(val) => val.get_property().map(|v| v.inner),
CssProperty::MarginRight(val) => val.get_property().map(|v| v.inner),
CssProperty::MarginTop(val) => val.get_property().map(|v| v.inner),
CssProperty::MarginBottom(val) => val.get_property().map(|v| v.inner),
CssProperty::MinWidth(val) => val.get_property().map(|v| v.inner),
CssProperty::MinHeight(val) => val.get_property().map(|v| v.inner),
CssProperty::MaxWidth(val) => val.get_property().map(|v| v.inner),
CssProperty::MaxHeight(val) => val.get_property().map(|v| v.inner),
CssProperty::SelectionRadius(val) => val.get_property().map(|v| v.inner),
_ => None,
}
};
let target_pixel_value = get_pixel_value(target_property)?;
let reference_pixel_value = get_pixel_value(reference_property)?;
let reference_px = match reference_pixel_value.metric {
SizeMetric::Px => reference_pixel_value.number.get(),
SizeMetric::Pt => reference_pixel_value.number.get() * PT_TO_PX,
SizeMetric::In => reference_pixel_value.number.get() * IN_TO_PX,
SizeMetric::Cm => reference_pixel_value.number.get() * CM_TO_PX,
SizeMetric::Mm => reference_pixel_value.number.get() * MM_TO_PX,
SizeMetric::Em
| SizeMetric::Rem
| SizeMetric::Percent
| SizeMetric::Vw
| SizeMetric::Vh
| SizeMetric::Vmin
| SizeMetric::Vmax => return None,
};
let resolved_px = match target_pixel_value.metric {
SizeMetric::Px => target_pixel_value.number.get(),
SizeMetric::Pt => target_pixel_value.number.get() * PT_TO_PX,
SizeMetric::In => target_pixel_value.number.get() * IN_TO_PX,
SizeMetric::Cm => target_pixel_value.number.get() * CM_TO_PX,
SizeMetric::Mm => target_pixel_value.number.get() * MM_TO_PX,
SizeMetric::Em | SizeMetric::Rem => target_pixel_value.number.get() * reference_px,
SizeMetric::Percent => target_pixel_value.number.get() / 100.0 * reference_px,
SizeMetric::Vw | SizeMetric::Vh | SizeMetric::Vmin | SizeMetric::Vmax => return None,
};
let resolved_pixel_value = PixelValue::px(resolved_px);
match target_property {
CssProperty::FontSize(_) => Some(CssProperty::FontSize(CssPropertyValue::Exact(
StyleFontSize {
inner: resolved_pixel_value,
},
))),
CssProperty::LetterSpacing(_) => Some(CssProperty::LetterSpacing(
CssPropertyValue::Exact(StyleLetterSpacing {
inner: resolved_pixel_value,
}),
)),
CssProperty::WordSpacing(_) => Some(CssProperty::WordSpacing(CssPropertyValue::Exact(
StyleWordSpacing {
inner: resolved_pixel_value,
},
))),
CssProperty::PaddingLeft(_) => Some(CssProperty::PaddingLeft(CssPropertyValue::Exact(
LayoutPaddingLeft {
inner: resolved_pixel_value,
},
))),
CssProperty::PaddingRight(_) => Some(CssProperty::PaddingRight(
CssPropertyValue::Exact(LayoutPaddingRight {
inner: resolved_pixel_value,
}),
)),
CssProperty::PaddingTop(_) => Some(CssProperty::PaddingTop(CssPropertyValue::Exact(
LayoutPaddingTop {
inner: resolved_pixel_value,
},
))),
CssProperty::PaddingBottom(_) => Some(CssProperty::PaddingBottom(
CssPropertyValue::Exact(LayoutPaddingBottom {
inner: resolved_pixel_value,
}),
)),
CssProperty::MarginLeft(_) => Some(CssProperty::MarginLeft(CssPropertyValue::Exact(
LayoutMarginLeft {
inner: resolved_pixel_value,
},
))),
CssProperty::MarginRight(_) => Some(CssProperty::MarginRight(CssPropertyValue::Exact(
LayoutMarginRight {
inner: resolved_pixel_value,
},
))),
CssProperty::MarginTop(_) => Some(CssProperty::MarginTop(CssPropertyValue::Exact(
LayoutMarginTop {
inner: resolved_pixel_value,
},
))),
CssProperty::MarginBottom(_) => Some(CssProperty::MarginBottom(
CssPropertyValue::Exact(LayoutMarginBottom {
inner: resolved_pixel_value,
}),
)),
CssProperty::MinWidth(_) => Some(CssProperty::MinWidth(CssPropertyValue::Exact(
LayoutMinWidth {
inner: resolved_pixel_value,
},
))),
CssProperty::MinHeight(_) => Some(CssProperty::MinHeight(CssPropertyValue::Exact(
LayoutMinHeight {
inner: resolved_pixel_value,
},
))),
CssProperty::MaxWidth(_) => Some(CssProperty::MaxWidth(CssPropertyValue::Exact(
LayoutMaxWidth {
inner: resolved_pixel_value,
},
))),
CssProperty::MaxHeight(_) => Some(CssProperty::MaxHeight(CssPropertyValue::Exact(
LayoutMaxHeight {
inner: resolved_pixel_value,
},
))),
CssProperty::SelectionRadius(_) => Some(CssProperty::SelectionRadius(
CssPropertyValue::Exact(SelectionRadius {
inner: resolved_pixel_value,
}),
)),
_ => None,
}
}
pub fn apply_ua_css(&mut self, node_data: &[NodeData]) {
use azul_css::props::property::CssPropertyType;
use azul_css::dynamic_selector::PseudoStateType;
let node_count = node_data.len();
if node_count == 0 {
return;
}
let mut prop_set: Vec<[u128; 2]> = vec![[0u128; 2]; node_count];
for (node_idx, props) in self.css_props.iter_node_slices() {
for p in props {
if p.state == PseudoStateType::Normal {
let d = p.prop_type as u16 as usize;
if d < 128 {
prop_set[node_idx][0] |= 1u128 << d;
} else {
prop_set[node_idx][1] |= 1u128 << (d - 128);
}
}
}
}
for (node_idx, props) in self.cascaded_props.iter_node_slices() {
for p in props {
if p.state == PseudoStateType::Normal {
let d = p.prop_type as u16 as usize;
if d < 128 {
prop_set[node_idx][0] |= 1u128 << d;
} else {
prop_set[node_idx][1] |= 1u128 << (d - 128);
}
}
}
}
for (node_idx, node) in node_data.iter().enumerate() {
for (prop, conds) in node.style.iter_inline_properties() {
let is_normal = conds.as_slice().is_empty();
if is_normal {
let d = prop.get_type() as u16 as usize;
if d < 128 {
prop_set[node_idx][0] |= 1u128 << d;
} else {
prop_set[node_idx][1] |= 1u128 << (d - 128);
}
}
}
}
let property_types = [
CssPropertyType::Display,
CssPropertyType::Width,
CssPropertyType::Height,
CssPropertyType::FontSize,
CssPropertyType::FontWeight,
CssPropertyType::FontFamily,
CssPropertyType::MarginTop,
CssPropertyType::MarginBottom,
CssPropertyType::MarginLeft,
CssPropertyType::MarginRight,
CssPropertyType::PaddingTop,
CssPropertyType::PaddingBottom,
CssPropertyType::PaddingLeft,
CssPropertyType::PaddingRight,
CssPropertyType::BorderTopStyle,
CssPropertyType::BorderTopWidth,
CssPropertyType::BorderTopColor,
CssPropertyType::BreakInside,
CssPropertyType::BreakAfter,
CssPropertyType::ListStyleType,
CssPropertyType::CounterReset,
CssPropertyType::TextDecoration,
CssPropertyType::TextAlign,
CssPropertyType::VerticalAlign,
CssPropertyType::Cursor,
];
for (node_index, node) in node_data.iter().enumerate() {
let node_type = &node.node_type;
for prop_type in &property_types {
let d = *prop_type as u16 as usize;
let has_prop = if d < 128 {
(prop_set[node_index][0] & (1u128 << d)) != 0
} else {
(prop_set[node_index][1] & (1u128 << (d - 128))) != 0
};
if has_prop {
continue;
}
if let Some(ua_prop) = crate::ua_css::get_ua_property(node_type, *prop_type) {
self.cascaded_props.push_to(node_index, StatefulCssProperty {
state: PseudoStateType::Normal,
prop_type: *prop_type,
property: ua_prop.clone(),
});
if d < 128 {
prop_set[node_index][0] |= 1u128 << d;
} else {
prop_set[node_index][1] |= 1u128 << (d - 128);
}
}
}
}
}
pub fn sort_cascaded_props(&mut self) {
self.cascaded_props.sort_each_and_flatten(|p| (p.state, p.prop_type));
}
pub fn compute_inherited_values(
&mut self,
node_hierarchy: &[NodeHierarchyItem],
node_data: &[NodeData],
) -> Vec<NodeId> {
if self.computed_values.len() < node_hierarchy.len() {
self.computed_values.resize(node_hierarchy.len(), Vec::new());
}
node_hierarchy
.iter()
.enumerate()
.filter_map(|(node_index, hierarchy_item)| {
let node_id = NodeId::new(node_index);
let parent_id = hierarchy_item.parent_id();
let parent_computed: Option<Vec<(CssPropertyType, CssPropertyWithOrigin)>> =
parent_id.and_then(|pid| self.computed_values.get(pid.index()).cloned());
let mut ctx = InheritanceContext {
node_id,
parent_id,
computed_values: Vec::new(),
};
if let Some(ref parent_values) = parent_computed {
Self::inherit_from_parent(&mut ctx, parent_values);
}
self.apply_cascade_properties(
&mut ctx,
node_id,
parent_computed.as_ref(),
node_data,
node_index,
);
let changed = self.store_if_changed(&ctx);
changed.then_some(node_id)
})
.collect()
}
fn inherit_from_parent(
ctx: &mut InheritanceContext,
parent_values: &[(CssPropertyType, CssPropertyWithOrigin)],
) {
for (prop_type, prop_with_origin) in
parent_values.iter().filter(|(pt, _)| pt.is_inheritable())
{
let entry = (*prop_type, CssPropertyWithOrigin {
property: prop_with_origin.property.clone(),
origin: CssPropertyOrigin::Inherited,
});
match ctx.computed_values.binary_search_by_key(prop_type, |(k, _)| *k) {
Ok(idx) => ctx.computed_values[idx] = entry,
Err(idx) => ctx.computed_values.insert(idx, entry),
}
}
}
fn apply_cascade_properties(
&self,
ctx: &mut InheritanceContext,
node_id: NodeId,
parent_computed: Option<&Vec<(CssPropertyType, CssPropertyWithOrigin)>>,
node_data: &[NodeData],
node_index: usize,
) {
{
let cascaded_slice = self.cascaded_props.get_slice(node_id.index());
for p in cascaded_slice {
if p.state == azul_css::dynamic_selector::PseudoStateType::Normal
&& Self::should_apply_cascaded(&ctx.computed_values, p.prop_type, &p.property) {
Self::process_property(ctx, &p.property, parent_computed);
}
}
}
{
let css_slice = self.css_props.get_slice(node_id.index());
for p in css_slice {
if p.state == azul_css::dynamic_selector::PseudoStateType::Normal {
Self::process_property(ctx, &p.property, parent_computed);
}
}
}
for (prop, conds) in node_data[node_index].style.iter_inline_properties() {
if conds.as_slice().is_empty() {
Self::process_property(ctx, prop, parent_computed);
}
}
if let Some(user_props) = self.user_overridden_properties.get(node_id.index()) {
for (_, prop) in user_props {
Self::process_property(ctx, prop, parent_computed);
}
}
}
fn should_apply_cascaded(
computed: &[(CssPropertyType, CssPropertyWithOrigin)],
prop_type: CssPropertyType,
_prop: &CssProperty,
) -> bool {
computed
.binary_search_by_key(&prop_type, |(k, _)| *k)
.map_or(true, |idx| computed[idx].1.origin == CssPropertyOrigin::Inherited)
}
fn process_property(
ctx: &mut InheritanceContext,
prop: &CssProperty,
parent_computed: Option<&Vec<(CssPropertyType, CssPropertyWithOrigin)>>,
) {
let prop_type = prop.get_type();
let resolved = if prop_type == CssPropertyType::FontSize {
Self::resolve_font_size_property(prop, parent_computed)
} else {
Self::resolve_other_property(prop, &ctx.computed_values)
};
let entry = (prop_type, CssPropertyWithOrigin {
property: resolved,
origin: CssPropertyOrigin::Own,
});
match ctx.computed_values.binary_search_by_key(&prop_type, |(k, _)| *k) {
Ok(idx) => ctx.computed_values[idx] = entry,
Err(idx) => ctx.computed_values.insert(idx, entry),
}
}
fn resolve_font_size_property(
prop: &CssProperty,
parent_computed: Option<&Vec<(CssPropertyType, CssPropertyWithOrigin)>>,
) -> CssProperty {
let parent_font_size = parent_computed
.and_then(|p| {
p.binary_search_by_key(&CssPropertyType::FontSize, |(k, _)| *k)
.ok()
.map(|idx| &p[idx].1)
});
parent_font_size.map_or_else(|| Self::resolve_font_size_to_pixels(
prop,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
), |pfs| Self::resolve_property_dependency(prop, &pfs.property).unwrap_or_else(
|| {
Self::resolve_font_size_to_pixels(
prop,
azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
)
},
))
}
fn resolve_other_property(
prop: &CssProperty,
computed: &[(CssPropertyType, CssPropertyWithOrigin)],
) -> CssProperty {
computed
.binary_search_by_key(&CssPropertyType::FontSize, |(k, _)| *k)
.ok()
.and_then(|idx| Self::resolve_property_dependency(prop, &computed[idx].1.property))
.unwrap_or_else(|| prop.clone())
}
fn resolve_font_size_to_pixels(prop: &CssProperty, reference_px: f32) -> CssProperty {
use azul_css::{
css::CssPropertyValue,
props::basic::{font::StyleFontSize, length::SizeMetric, pixel::PixelValue},
};
let CssProperty::FontSize(css_val) = prop else {
return prop.clone();
};
let Some(font_size) = css_val.get_property() else {
return prop.clone();
};
let resolved_px = match font_size.inner.metric {
SizeMetric::Px => font_size.inner.number.get(),
SizeMetric::Pt => font_size.inner.number.get() * PT_TO_PX,
SizeMetric::In => font_size.inner.number.get() * IN_TO_PX,
SizeMetric::Cm => font_size.inner.number.get() * CM_TO_PX,
SizeMetric::Mm => font_size.inner.number.get() * MM_TO_PX,
SizeMetric::Em => font_size.inner.number.get() * reference_px,
SizeMetric::Rem => {
font_size.inner.number.get() * azul_css::props::basic::pixel::DEFAULT_FONT_SIZE
}
SizeMetric::Percent => font_size.inner.number.get() / 100.0 * reference_px,
SizeMetric::Vw | SizeMetric::Vh | SizeMetric::Vmin | SizeMetric::Vmax => {
return prop.clone();
}
};
CssProperty::FontSize(CssPropertyValue::Exact(StyleFontSize {
inner: PixelValue::px(resolved_px),
}))
}
fn has_relative_font_size_unit(prop: &CssProperty) -> bool {
use azul_css::props::basic::length::SizeMetric;
let CssProperty::FontSize(css_val) = prop else {
return false;
};
css_val
.get_property()
.is_some_and(|fs| {
matches!(
fs.inner.metric,
SizeMetric::Em | SizeMetric::Rem | SizeMetric::Percent
)
})
}
fn store_if_changed(&mut self, ctx: &InheritanceContext) -> bool {
let values_changed = self
.computed_values
.get(ctx.node_id.index()) != Some(&ctx.computed_values);
self.computed_values[ctx.node_id.index()].clone_from(&ctx.computed_values);
values_changed
}
}
struct InheritanceContext {
node_id: NodeId,
parent_id: Option<NodeId>,
computed_values: Vec<(CssPropertyType, CssPropertyWithOrigin)>,
}
impl CssPropertyCache {
pub(crate) fn invalidate_resolved_cache(&mut self) {
self.compact_cache = None;
}
}
#[cfg(test)]
#[allow(clippy::float_cmp, clippy::too_many_lines)]
mod autotest_generated {
use azul_css::{
css::CssPropertyValue,
dynamic_selector::{
CssPropertyWithConditions, DynamicSelector, DynamicSelectorContext, PseudoStateType,
},
props::{
basic::{length::SizeMetric, pixel::PixelValue},
layout::{
LayoutFlexBasis, LayoutInsetBottom, LayoutLeft, LayoutMarginTop, LayoutMaxWidth,
LayoutMinWidth, LayoutOverflow, LayoutPaddingLeft, LayoutRight, LayoutTop,
},
style::LayoutBorderLeftWidth,
},
};
use super::*;
fn close(a: f32, b: f32) -> bool {
(a - b).abs() < 0.01
}
fn n0() -> NodeId {
NodeId::new(0)
}
fn normal() -> StyledNodeState {
StyledNodeState::default()
}
fn div_with(props: Vec<CssProperty>) -> NodeData {
let mut nd = NodeData::create_div();
for property in props {
nd.add_css_property(CssPropertyWithConditions {
property,
apply_if: Vec::new().into(),
});
}
nd
}
fn div_with_pseudo(props: Vec<CssProperty>, state: PseudoStateType) -> NodeData {
let mut nd = NodeData::create_div();
for property in props {
nd.add_css_property(CssPropertyWithConditions {
property,
apply_if: vec![DynamicSelector::PseudoState(state)].into(),
});
}
nd
}
fn width_px(v: f32) -> CssProperty {
CssProperty::Width(CssPropertyValue::Exact(LayoutWidth::Px(PixelValue::px(v))))
}
fn width_pct(v: f32) -> CssProperty {
CssProperty::Width(CssPropertyValue::Exact(LayoutWidth::Px(PixelValue::percent(
v,
))))
}
fn font_size(pv: PixelValue) -> CssProperty {
CssProperty::FontSize(CssPropertyValue::Exact(StyleFontSize { inner: pv }))
}
fn font_size_parts(p: &CssProperty) -> Option<(SizeMetric, f32)> {
match p {
CssProperty::FontSize(v) => v
.get_property()
.map(|fs| (fs.inner.metric, fs.inner.number.get())),
_ => None,
}
}
fn stateful(state: PseudoStateType, property: CssProperty) -> StatefulCssProperty {
StatefulCssProperty {
state,
prop_type: property.get_type(),
property,
}
}
#[test]
fn flatvecvec_new_zero_is_empty() {
let f = FlatVecVec::<i32>::new(0);
assert_eq!(f.len(), 0);
assert!(f.is_empty());
assert!(f.is_flattened());
assert!(f.get_slice(0).is_empty());
}
#[test]
fn flatvecvec_new_invariants_hold() {
let f = FlatVecVec::<i32>::new(3);
assert_eq!(f.len(), 3);
assert!(!f.is_empty());
assert!(!f.is_flattened(), "fresh multi-slot vec is in build phase");
assert_eq!(f.build_get(0), Some(&Vec::new()));
assert_eq!(f.build_get(2), Some(&Vec::new()));
assert_eq!(f.build_get(3), None, "one past the end");
assert_eq!(f.build_get(usize::MAX), None);
assert!(f.get_slice(0).is_empty());
}
#[test]
fn flatvecvec_default_is_neutral() {
let f = FlatVecVec::<i32>::default();
assert_eq!(f.len(), 0);
assert!(f.is_empty());
assert_eq!(f.build_get(0), None);
assert!(f.get_slice(0).is_empty());
}
#[test]
fn flatvecvec_get_slice_out_of_bounds_is_empty_in_both_phases() {
let mut f = FlatVecVec::<i32>::new(2);
f.push_to(0, 7);
assert_eq!(f.get_slice(0), &[7]);
assert!(f.get_slice(2).is_empty());
assert!(f.get_slice(usize::MAX).is_empty());
f.flatten();
assert_eq!(f.get_slice(0), &[7]);
assert!(f.get_slice(2).is_empty());
assert!(f.get_slice(usize::MAX).is_empty());
}
#[test]
#[should_panic(expected = "index out of bounds")]
fn flatvecvec_push_to_out_of_bounds_panics() {
let mut f = FlatVecVec::<i32>::new(1);
f.push_to(1, 0);
}
#[test]
#[should_panic(expected = "index out of bounds")]
fn flatvecvec_push_to_after_flatten_panics() {
let mut f = FlatVecVec::<i32>::new(1);
f.flatten();
f.push_to(0, 0);
}
#[test]
#[should_panic(expected = "index out of bounds")]
fn flatvecvec_build_mut_out_of_bounds_panics() {
let mut f = FlatVecVec::<i32>::new(1);
let _ = f.build_mut(usize::MAX);
}
#[test]
fn flatvecvec_build_iter_mut_visits_every_slot() {
let mut f = FlatVecVec::<i32>::new(3);
f.push_to(0, 1);
f.push_to(2, 2);
let mut visited = 0;
for v in f.build_iter_mut() {
visited += 1;
v.clear();
}
assert_eq!(visited, 3);
assert!(f.get_slice(0).is_empty());
assert!(f.get_slice(2).is_empty());
}
#[test]
fn flatvecvec_build_get_returns_none_once_flattened() {
let mut f = FlatVecVec::<i32>::new(1);
f.push_to(0, 5);
f.flatten();
assert_eq!(f.build_get(0), None);
assert_eq!(f.get_slice(0), &[5]);
}
#[test]
fn flatvecvec_heap_bytes_zero_and_empty() {
let f = FlatVecVec::<i32>::default();
assert_eq!(f.heap_bytes(0), 0, "empty vec, zero element size");
assert_eq!(f.heap_bytes(usize::MAX), 0);
assert_eq!(f.heap_bytes(size_of::<i32>()), 0);
}
#[test]
fn flatvecvec_heap_bytes_counts_build_and_flat_storage() {
let mut f = FlatVecVec::<i32>::new(4);
assert!(f.heap_bytes(0) >= 4 * size_of::<Vec<i32>>());
f.push_to(0, 1);
f.push_to(0, 2);
let build_bytes = f.heap_bytes(size_of::<i32>());
assert!(build_bytes > 0);
f.flatten();
let flat_bytes = f.heap_bytes(size_of::<i32>());
assert!(flat_bytes >= 2 * size_of::<i32>() + 4 * size_of::<(u32, u32)>());
}
#[test]
fn flatvecvec_sort_each_and_flatten_keeps_last_of_equal_keys() {
let mut f = FlatVecVec::<(i32, i32)>::new(1);
f.push_to(0, (1, 10));
f.push_to(0, (1, 20)); f.push_to(0, (0, 30));
f.sort_each_and_flatten(|p| p.0);
assert!(f.is_flattened());
assert_eq!(f.get_slice(0), &[(0, 30), (1, 20)]);
}
#[test]
fn flatvecvec_sort_each_and_flatten_on_empty_slots() {
let mut f = FlatVecVec::<i32>::new(3);
f.push_to(1, 42);
f.sort_each_and_flatten(|v| *v);
assert_eq!(f.len(), 3);
assert!(f.get_slice(0).is_empty());
assert_eq!(f.get_slice(1), &[42]);
assert!(f.get_slice(2).is_empty());
}
#[test]
fn flatvecvec_sort_each_and_flatten_on_zero_nodes_does_not_panic() {
let mut f = FlatVecVec::<i32>::new(0);
f.sort_each_and_flatten(|v| *v);
assert_eq!(f.len(), 0);
assert!(f.get_slice(0).is_empty());
}
#[test]
fn flatvecvec_flatten_does_not_deduplicate() {
let mut f = FlatVecVec::<i32>::new(2);
f.push_to(0, 5);
f.push_to(0, 5);
f.push_to(1, 9);
f.flatten();
assert!(f.is_flattened());
assert_eq!(f.get_slice(0), &[5, 5], "flatten() must not dedup");
assert_eq!(f.get_slice(1), &[9]);
}
#[test]
fn flatvecvec_retain_before_flatten_is_a_noop() {
let mut f = FlatVecVec::<i32>::new(1);
f.push_to(0, 1);
f.push_to(0, 2);
f.retain(|_| false);
assert_eq!(f.get_slice(0), &[1, 2], "build-phase data left untouched");
}
#[test]
fn flatvecvec_retain_preserves_per_node_order() {
let mut f = FlatVecVec::<i32>::new(2);
for v in [1, 2, 3, 4] {
f.push_to(0, v);
}
f.push_to(1, 5);
f.flatten();
f.retain(|v| v % 2 == 0);
assert_eq!(f.get_slice(0), &[2, 4]);
assert!(f.get_slice(1).is_empty());
assert_eq!(f.len(), 2, "node slots survive an empty retain");
}
#[test]
fn flatvecvec_retain_dropping_everything_leaves_empty_slices() {
let mut f = FlatVecVec::<i32>::new(2);
f.push_to(0, 1);
f.push_to(1, 2);
f.flatten();
f.retain(|_| false);
assert_eq!(f.len(), 2);
assert!(f.get_slice(0).is_empty());
assert!(f.get_slice(1).is_empty());
}
#[test]
fn flatvecvec_retain_with_node_index_sees_owning_node() {
let mut f = FlatVecVec::<i32>::new(3);
f.push_to(0, 10);
f.push_to(1, 11);
f.push_to(2, 12);
f.flatten();
f.retain_with_node_index(|idx, _| idx == 1);
assert!(f.get_slice(0).is_empty());
assert_eq!(f.get_slice(1), &[11]);
assert!(f.get_slice(2).is_empty());
}
#[test]
fn flatvecvec_retain_with_node_index_before_flatten_is_a_noop() {
let mut f = FlatVecVec::<i32>::new(1);
f.push_to(0, 1);
f.retain_with_node_index(|_, _| false);
assert_eq!(f.get_slice(0), &[1]);
}
#[test]
fn flatvecvec_iter_node_slices_covers_all_nodes_in_both_phases() {
let mut f = FlatVecVec::<i32>::new(3);
f.push_to(1, 7);
let build: Vec<(usize, Vec<i32>)> = f
.iter_node_slices()
.map(|(i, s)| (i, s.to_vec()))
.collect();
assert_eq!(build, vec![(0, vec![]), (1, vec![7]), (2, vec![])]);
f.flatten();
let flat: Vec<(usize, Vec<i32>)> = f
.iter_node_slices()
.map(|(i, s)| (i, s.to_vec()))
.collect();
assert_eq!(flat, build, "iteration is phase-independent");
}
#[test]
fn flatvecvec_iter_node_slices_on_empty_yields_nothing() {
let f = FlatVecVec::<i32>::new(0);
assert_eq!(f.iter_node_slices().count(), 0);
}
#[test]
fn flatvecvec_extend_from_both_in_build_phase() {
let mut a = FlatVecVec::<i32>::new(1);
a.push_to(0, 1);
let mut b = FlatVecVec::<i32>::new(2);
b.push_to(0, 2);
b.push_to(1, 3);
a.extend_from(&mut b);
assert_eq!(a.len(), 3);
assert_eq!(a.get_slice(0), &[1]);
assert_eq!(a.get_slice(1), &[2]);
assert_eq!(a.get_slice(2), &[3]);
assert_eq!(b.len(), 0, "other is drained");
}
#[test]
fn flatvecvec_extend_from_both_flattened_rebases_offsets() {
let mut a = FlatVecVec::<i32>::new(2);
a.push_to(0, 1);
a.push_to(1, 2);
a.flatten();
let mut b = FlatVecVec::<i32>::new(2);
b.push_to(0, 3);
b.push_to(1, 4);
b.flatten();
a.extend_from(&mut b);
assert_eq!(a.len(), 4);
assert_eq!(a.get_slice(0), &[1]);
assert_eq!(a.get_slice(1), &[2]);
assert_eq!(a.get_slice(2), &[3], "offsets rebased onto a's flat data");
assert_eq!(a.get_slice(3), &[4]);
}
#[test]
fn flatvecvec_extend_from_across_phases_discards_self_flat_data() {
let mut a = FlatVecVec::<i32>::new(1);
a.push_to(0, 1);
a.flatten();
let mut b = FlatVecVec::<i32>::new(1);
b.push_to(0, 2);
a.extend_from(&mut b); assert_eq!(a.len(), 1);
assert_eq!(
a.get_slice(0),
&[2],
"a's own flattened item (1) is silently lost"
);
}
#[test]
fn flatvecvec_eq_within_the_same_phase() {
let mut a = FlatVecVec::<i32>::new(1);
a.push_to(0, 1);
let mut b = FlatVecVec::<i32>::new(1);
b.push_to(0, 1);
assert_eq!(a, b);
b.push_to(0, 2);
assert_ne!(a, b);
a.flatten();
let mut c = FlatVecVec::<i32>::new(1);
c.push_to(0, 1);
c.flatten();
assert_eq!(a, c);
}
#[test]
fn breakdown_total_bytes_sums_subfields_and_excludes_node_count() {
let b = CssPropertyCacheBreakdown {
node_count: 999_999,
cascaded_props_bytes: 1,
css_props_bytes: 2,
computed_values_bytes: 4,
user_overridden_bytes: 8,
global_css_props_bytes: 16,
compact_cache_bytes: 32,
resolved_font_sizes_bytes: 64,
};
assert_eq!(b.total_bytes(), 127, "node_count is not a byte count");
}
#[test]
fn breakdown_total_bytes_default_is_zero_and_max_single_field_does_not_overflow() {
assert_eq!(CssPropertyCacheBreakdown::default().total_bytes(), 0);
let b = CssPropertyCacheBreakdown {
cascaded_props_bytes: usize::MAX,
..Default::default()
};
assert_eq!(b.total_bytes(), usize::MAX);
}
#[test]
fn cache_empty_zero_is_neutral() {
let c = CssPropertyCache::empty(0);
assert_eq!(c.node_count, 0);
assert!(c.css_props.is_empty());
assert!(c.cascaded_props.is_empty());
assert!(c.computed_values.is_empty());
assert!(c.user_overridden_properties.is_empty());
assert!(c.global_css_props.is_empty());
assert!(c.compact_cache.is_none());
let b = c.memory_breakdown();
assert_eq!(b.node_count, 0);
assert_eq!(b.total_bytes(), 0, "a zero-node cache retains no heap");
}
#[test]
fn cache_empty_invariants_hold() {
let c = CssPropertyCache::empty(7);
assert_eq!(c.node_count, 7);
assert_eq!(c.css_props.len(), 7);
assert_eq!(c.cascaded_props.len(), 7);
assert!(!c.css_props.is_flattened(), "starts in build phase");
assert!(c.compact_cache.is_none());
let b = c.memory_breakdown();
assert_eq!(b.node_count, 7);
assert!(b.total_bytes() > 0);
assert_eq!(b.compact_cache_bytes, 0);
assert_eq!(b.resolved_font_sizes_bytes, 0);
}
#[test]
fn cache_invalidate_resolved_font_sizes_clears_the_once_lock() {
let mut c = CssPropertyCache::empty(1);
assert!(c.resolved_font_sizes_px.set(vec![16.0]).is_ok());
assert!(c.resolved_font_sizes_px.get().is_some());
c.invalidate_resolved_font_sizes();
assert!(
c.resolved_font_sizes_px.get().is_none(),
"next read must recompute"
);
assert!(c.resolved_font_sizes_px.set(vec![12.0]).is_ok());
}
#[test]
fn cache_append_sums_nodes_and_invalidates_derived_caches() {
let mut a = CssPropertyCache::empty(2);
let mut b = CssPropertyCache::empty(3);
assert!(a.resolved_font_sizes_px.set(vec![16.0, 16.0]).is_ok());
a.append(&mut b);
assert_eq!(a.node_count, 5);
assert_eq!(a.css_props.len(), 5);
assert_eq!(a.cascaded_props.len(), 5);
assert!(
a.resolved_font_sizes_px.get().is_none(),
"node indices shifted"
);
assert!(a.compact_cache.is_none());
}
#[test]
fn cache_append_of_empty_cache_is_a_noop_on_node_count() {
let mut a = CssPropertyCache::empty(2);
let mut b = CssPropertyCache::empty(0);
a.append(&mut b);
assert_eq!(a.node_count, 2);
assert_eq!(a.css_props.len(), 2);
}
#[test]
fn cache_invalidate_resolved_cache_drops_compact_cache() {
let mut c = CssPropertyCache::empty(1);
c.invalidate_resolved_cache();
assert!(c.compact_cache.is_none());
}
#[test]
fn cache_ptr_new_and_downcast_roundtrip() {
let mut p = CssPropertyCachePtr::new(CssPropertyCache::empty(4));
assert!(p.run_destructor);
assert_eq!(p.downcast_mut().node_count, 4);
p.downcast_mut().node_count = 9;
assert_eq!(p.downcast_mut().node_count, 9, "downcast_mut aliases the box");
}
#[test]
fn overflow_predicates_default_to_visible_for_a_bare_div() {
let c = CssPropertyCache::empty(1);
let nd = NodeData::create_div();
assert!(c.is_horizontal_overflow_visible(&nd, &n0(), &normal()));
assert!(c.is_vertical_overflow_visible(&nd, &n0(), &normal()));
assert!(!c.is_horizontal_overflow_hidden(&nd, &n0(), &normal()));
assert!(!c.is_vertical_overflow_hidden(&nd, &n0(), &normal()));
}
#[test]
fn overflow_predicates_are_per_axis() {
let c = CssPropertyCache::empty(1);
let nd = div_with(vec![CssProperty::OverflowX(CssPropertyValue::Exact(
LayoutOverflow::Hidden,
))]);
assert!(c.is_horizontal_overflow_hidden(&nd, &n0(), &normal()));
assert!(!c.is_horizontal_overflow_visible(&nd, &n0(), &normal()));
assert!(!c.is_vertical_overflow_hidden(&nd, &n0(), &normal()));
assert!(c.is_vertical_overflow_visible(&nd, &n0(), &normal()));
}
#[test]
fn overflow_predicates_do_not_panic_on_an_out_of_range_node_id() {
let c = CssPropertyCache::empty(0);
let nd = NodeData::create_div();
let far = NodeId::new(999_999);
assert!(c.is_horizontal_overflow_visible(&nd, &far, &normal()));
assert!(!c.is_vertical_overflow_hidden(&nd, &far, &normal()));
}
#[test]
fn has_border_false_without_and_true_with_a_border_width() {
let c = CssPropertyCache::empty(1);
assert!(!c.has_border(&NodeData::create_div(), &n0(), &normal()));
let bordered = div_with(vec![CssProperty::BorderLeftWidth(CssPropertyValue::Exact(
LayoutBorderLeftWidth {
inner: PixelValue::px(2.0),
},
))]);
assert!(c.has_border(&bordered, &n0(), &normal()));
}
#[test]
fn has_box_shadow_false_for_a_bare_div() {
let c = CssPropertyCache::empty(1);
assert!(!c.has_box_shadow(&NodeData::create_div(), &n0(), &normal()));
assert!(!c.has_box_shadow(&NodeData::create_div(), &NodeId::new(500), &normal()));
}
#[test]
fn or_default_getters_fall_back_to_the_css_defaults() {
let c = CssPropertyCache::empty(1);
let nd = NodeData::create_div();
assert_eq!(
c.get_font_size_or_default(&nd, &n0(), &normal()),
azul_css::defaults::DEFAULT_FONT_SIZE
);
assert_eq!(
c.get_text_color_or_default(&nd, &n0(), &normal()),
azul_css::defaults::DEFAULT_TEXT_COLOR
);
let fams = c.get_font_id_or_default(&nd, &n0(), &normal());
assert_eq!(fams.as_ref().len(), 1);
match &fams.as_ref()[0] {
StyleFontFamily::System(s) => {
assert_eq!(s.as_str(), azul_css::defaults::DEFAULT_FONT_ID);
}
other => panic!("expected the default System font family, got {other:?}"),
}
}
#[test]
fn get_font_size_or_default_prefers_the_inline_value() {
let c = CssPropertyCache::empty(1);
let nd = div_with(vec![font_size(PixelValue::px(42.0))]);
let fs = c.get_font_size_or_default(&nd, &n0(), &normal());
assert!(close(fs.inner.number.get(), 42.0));
assert_eq!(fs.inner.metric, SizeMetric::Px);
}
#[test]
fn or_default_getters_survive_an_out_of_range_node_id() {
let c = CssPropertyCache::empty(0);
let nd = NodeData::create_div();
let far = NodeId::new(usize::MAX / 2);
assert_eq!(
c.get_font_size_or_default(&nd, &far, &normal()),
azul_css::defaults::DEFAULT_FONT_SIZE
);
assert_eq!(c.get_font_id_or_default(&nd, &far, &normal()).as_ref().len(), 1);
}
#[test]
fn calc_width_is_zero_when_unset() {
let c = CssPropertyCache::empty(1);
let nd = NodeData::create_div();
assert_eq!(c.calc_width(&nd, &n0(), &normal(), 800.0), 0.0);
assert_eq!(c.calc_width(&nd, &n0(), &normal(), 0.0), 0.0);
assert_eq!(c.calc_height(&nd, &n0(), &normal(), f32::NAN), 0.0);
}
#[test]
fn calc_width_resolves_px_and_percent() {
let c = CssPropertyCache::empty(1);
let px = div_with(vec![width_px(100.0)]);
assert!(close(c.calc_width(&px, &n0(), &normal(), 800.0), 100.0));
assert!(close(c.calc_width(&px, &n0(), &normal(), 0.0), 100.0));
let pct = div_with(vec![width_pct(50.0)]);
assert!(close(c.calc_width(&pct, &n0(), &normal(), 800.0), 400.0));
assert!(close(c.calc_width(&pct, &n0(), &normal(), 0.0), 0.0));
}
#[test]
fn calc_width_with_a_negative_reference_is_negative_not_clamped() {
let c = CssPropertyCache::empty(1);
let pct = div_with(vec![width_pct(50.0)]);
assert!(close(c.calc_width(&pct, &n0(), &normal(), -800.0), -400.0));
}
#[test]
fn calc_width_with_nan_and_infinite_references_is_defined() {
let c = CssPropertyCache::empty(1);
let pct = div_with(vec![width_pct(50.0)]);
assert!(c.calc_width(&pct, &n0(), &normal(), f32::NAN).is_nan());
assert_eq!(
c.calc_width(&pct, &n0(), &normal(), f32::INFINITY),
f32::INFINITY
);
assert_eq!(
c.calc_width(&pct, &n0(), &normal(), f32::NEG_INFINITY),
f32::NEG_INFINITY
);
}
#[test]
fn calc_width_saturates_non_finite_pixel_values_at_construction() {
let c = CssPropertyCache::empty(1);
let nan = div_with(vec![width_px(f32::NAN)]);
assert_eq!(c.calc_width(&nan, &n0(), &normal(), 800.0), 0.0);
let inf = div_with(vec![width_px(f32::INFINITY)]);
let got = c.calc_width(&inf, &n0(), &normal(), 800.0);
assert!(got.is_finite() && got > 0.0, "saturated, got {got}");
let neg_inf = div_with(vec![width_px(f32::NEG_INFINITY)]);
let got = c.calc_width(&neg_inf, &n0(), &normal(), 800.0);
assert!(got.is_finite() && got < 0.0, "saturated, got {got}");
let huge = div_with(vec![width_px(f32::MAX)]);
assert!(c.calc_width(&huge, &n0(), &normal(), 800.0).is_finite());
}
#[test]
fn calc_width_of_auto_and_intrinsic_keywords_is_zero() {
let c = CssPropertyCache::empty(1);
let auto = div_with(vec![CssProperty::Width(CssPropertyValue::Auto)]);
assert_eq!(c.calc_width(&auto, &n0(), &normal(), 800.0), 0.0);
let min_content = div_with(vec![CssProperty::Width(CssPropertyValue::Exact(
LayoutWidth::MinContent,
))]);
assert_eq!(c.calc_width(&min_content, &n0(), &normal(), 800.0), 0.0);
}
#[test]
fn calc_height_mirrors_calc_width() {
let c = CssPropertyCache::empty(1);
let nd = div_with(vec![CssProperty::Height(CssPropertyValue::Exact(
LayoutHeight::Px(PixelValue::percent(25.0)),
))]);
assert!(close(c.calc_height(&nd, &n0(), &normal(), 400.0), 100.0));
assert!(c.calc_height(&nd, &n0(), &normal(), f32::NAN).is_nan());
}
#[test]
fn calc_min_width_defaults_to_zero_and_max_width_defaults_to_none() {
let c = CssPropertyCache::empty(1);
let nd = NodeData::create_div();
assert_eq!(c.calc_min_width(&nd, &n0(), &normal(), 800.0), 0.0);
assert_eq!(c.calc_min_height(&nd, &n0(), &normal(), 600.0), 0.0);
assert_eq!(c.calc_max_width(&nd, &n0(), &normal(), 800.0), None);
assert_eq!(c.calc_max_height(&nd, &n0(), &normal(), 600.0), None);
}
#[test]
fn calc_min_max_width_resolve_percentages_and_propagate_nan() {
let c = CssPropertyCache::empty(1);
let nd = div_with(vec![
CssProperty::MinWidth(CssPropertyValue::Exact(LayoutMinWidth {
inner: PixelValue::percent(10.0),
})),
CssProperty::MaxWidth(CssPropertyValue::Exact(LayoutMaxWidth {
inner: PixelValue::percent(90.0),
})),
]);
assert!(close(c.calc_min_width(&nd, &n0(), &normal(), 1000.0), 100.0));
assert!(close(
c.calc_max_width(&nd, &n0(), &normal(), 1000.0).unwrap(),
900.0
));
assert!(c.calc_min_width(&nd, &n0(), &normal(), f32::NAN).is_nan());
assert!(c
.calc_max_width(&nd, &n0(), &normal(), f32::NAN)
.unwrap()
.is_nan());
}
#[test]
fn calc_inset_getters_are_none_when_unset_and_some_when_set() {
let c = CssPropertyCache::empty(1);
let bare = NodeData::create_div();
assert_eq!(c.calc_left(&bare, &n0(), &normal(), 800.0), None);
assert_eq!(c.calc_right(&bare, &n0(), &normal(), 800.0), None);
assert_eq!(c.calc_top(&bare, &n0(), &normal(), 600.0), None);
assert_eq!(c.calc_bottom(&bare, &n0(), &normal(), 600.0), None);
let inset = div_with(vec![
CssProperty::Left(CssPropertyValue::Exact(LayoutLeft {
inner: PixelValue::px(5.0),
})),
CssProperty::Right(CssPropertyValue::Exact(LayoutRight {
inner: PixelValue::percent(10.0),
})),
CssProperty::Top(CssPropertyValue::Exact(LayoutTop {
inner: PixelValue::px(-7.0),
})),
CssProperty::Bottom(CssPropertyValue::Exact(LayoutInsetBottom {
inner: PixelValue::px(0.0),
})),
]);
assert!(close(c.calc_left(&inset, &n0(), &normal(), 800.0).unwrap(), 5.0));
assert!(close(
c.calc_right(&inset, &n0(), &normal(), 800.0).unwrap(),
80.0
));
assert!(close(
c.calc_top(&inset, &n0(), &normal(), 600.0).unwrap(),
-7.0
));
assert_eq!(c.calc_bottom(&inset, &n0(), &normal(), 600.0), Some(0.0));
}
#[test]
fn calc_padding_margin_border_default_to_zero() {
let c = CssPropertyCache::empty(1);
let nd = NodeData::create_div();
assert_eq!(c.calc_padding_left(&nd, &n0(), &normal(), 800.0), 0.0);
assert_eq!(c.calc_padding_right(&nd, &n0(), &normal(), 800.0), 0.0);
assert_eq!(c.calc_padding_top(&nd, &n0(), &normal(), 600.0), 0.0);
assert_eq!(c.calc_padding_bottom(&nd, &n0(), &normal(), 600.0), 0.0);
assert_eq!(c.calc_margin_left(&nd, &n0(), &normal(), 800.0), 0.0);
assert_eq!(c.calc_margin_right(&nd, &n0(), &normal(), 800.0), 0.0);
assert_eq!(c.calc_margin_top(&nd, &n0(), &normal(), 600.0), 0.0);
assert_eq!(c.calc_margin_bottom(&nd, &n0(), &normal(), 600.0), 0.0);
assert_eq!(c.calc_border_left_width(&nd, &n0(), &normal(), 800.0), 0.0);
assert_eq!(c.calc_border_right_width(&nd, &n0(), &normal(), 800.0), 0.0);
assert_eq!(c.calc_border_top_width(&nd, &n0(), &normal(), 600.0), 0.0);
assert_eq!(c.calc_border_bottom_width(&nd, &n0(), &normal(), 600.0), 0.0);
}
#[test]
fn calc_padding_em_uses_the_default_font_size_not_the_reference() {
let c = CssPropertyCache::empty(1);
let nd = div_with(vec![CssProperty::PaddingLeft(CssPropertyValue::Exact(
LayoutPaddingLeft {
inner: PixelValue::em(2.0),
},
))]);
assert!(close(c.calc_padding_left(&nd, &n0(), &normal(), 800.0), 32.0));
assert!(close(c.calc_padding_left(&nd, &n0(), &normal(), 0.0), 32.0));
assert!(close(
c.calc_padding_left(&nd, &n0(), &normal(), f32::NAN),
32.0
));
}
#[test]
fn calc_margin_and_border_resolve_px_and_percent() {
let c = CssPropertyCache::empty(1);
let nd = div_with(vec![
CssProperty::MarginTop(CssPropertyValue::Exact(LayoutMarginTop {
inner: PixelValue::percent(50.0),
})),
CssProperty::BorderLeftWidth(CssPropertyValue::Exact(LayoutBorderLeftWidth {
inner: PixelValue::px(3.0),
})),
]);
assert!(close(c.calc_margin_top(&nd, &n0(), &normal(), 200.0), 100.0));
assert!(close(
c.calc_border_left_width(&nd, &n0(), &normal(), 800.0),
3.0
));
assert!(c.calc_margin_top(&nd, &n0(), &normal(), f32::NAN).is_nan());
}
#[test]
fn calc_getters_do_not_panic_on_an_out_of_range_node_id() {
let c = CssPropertyCache::empty(0);
let nd = NodeData::create_div();
let far = NodeId::new(usize::MAX / 2);
assert_eq!(c.calc_width(&nd, &far, &normal(), 800.0), 0.0);
assert_eq!(c.calc_max_height(&nd, &far, &normal(), 600.0), None);
assert_eq!(c.calc_padding_top(&nd, &far, &normal(), f32::INFINITY), 0.0);
}
#[test]
fn slow_path_only_needed_for_non_px_pixel_values() {
assert!(!property_needs_slow_path_after_compact(&width_px(10.0)));
assert!(property_needs_slow_path_after_compact(&width_pct(50.0)));
assert!(!property_needs_slow_path_after_compact(&CssProperty::Height(
CssPropertyValue::Exact(LayoutHeight::Px(PixelValue::px(1.0)))
)));
assert!(property_needs_slow_path_after_compact(&CssProperty::Height(
CssPropertyValue::Exact(LayoutHeight::Px(PixelValue::em(1.0)))
)));
}
#[test]
fn slow_path_covers_the_plain_pixelvalue_wrappers() {
assert!(property_needs_slow_path_after_compact(&font_size(
PixelValue::rem(2.0)
)));
assert!(!property_needs_slow_path_after_compact(&font_size(
PixelValue::px(16.0)
)));
assert!(property_needs_slow_path_after_compact(
&CssProperty::MinWidth(CssPropertyValue::Exact(LayoutMinWidth {
inner: PixelValue::percent(10.0),
}))
));
assert!(!property_needs_slow_path_after_compact(
&CssProperty::PaddingLeft(CssPropertyValue::Exact(LayoutPaddingLeft {
inner: PixelValue::px(4.0),
}))
));
}
#[test]
fn slow_path_handles_flex_basis_and_non_pixel_properties() {
assert!(property_needs_slow_path_after_compact(
&CssProperty::FlexBasis(CssPropertyValue::Exact(LayoutFlexBasis::Exact(
PixelValue::percent(50.0)
)))
));
assert!(!property_needs_slow_path_after_compact(
&CssProperty::FlexBasis(CssPropertyValue::Exact(LayoutFlexBasis::Auto))
));
assert!(!property_needs_slow_path_after_compact(&CssProperty::Width(
CssPropertyValue::Auto
)));
assert!(!property_needs_slow_path_after_compact(
&CssProperty::const_none(CssPropertyType::Display)
));
assert!(!property_needs_slow_path_after_compact(
&CssProperty::const_none(CssPropertyType::BackgroundContent)
));
}
#[test]
fn clone_inheritable_property_round_trips_heap_and_pod_variants() {
let font_family = CssProperty::FontFamily(CssPropertyValue::Exact(
vec![StyleFontFamily::System(AzString::from_const_str("serif"))].into(),
));
assert_eq!(clone_inheritable_property(&font_family), font_family);
for p in [
CssProperty::const_none(CssPropertyType::Cursor),
CssProperty::const_none(CssPropertyType::TextColor),
CssProperty::const_none(CssPropertyType::BackgroundContent),
CssProperty::const_none(CssPropertyType::Transform),
CssProperty::const_none(CssPropertyType::Content),
width_px(3.0),
font_size(PixelValue::em(1.5)),
] {
assert_eq!(clone_inheritable_property(&p), p, "clone must be identity");
assert_eq!(clone_inheritable_property(&p).get_type(), p.get_type());
}
}
fn sorted_stateful_fixture() -> Vec<StatefulCssProperty> {
let mut v = vec![
stateful(PseudoStateType::Normal, width_px(1.0)),
stateful(
PseudoStateType::Normal,
CssProperty::const_none(CssPropertyType::Display),
),
stateful(PseudoStateType::Hover, width_px(2.0)),
];
v.sort_by_key(|p| (p.state, p.prop_type));
v
}
#[test]
fn find_in_stateful_on_an_empty_slice_is_none() {
assert!(CssPropertyCache::find_in_stateful(
&[],
PseudoStateType::Normal,
&CssPropertyType::Width
)
.is_none());
}
#[test]
fn find_in_stateful_is_keyed_on_both_state_and_prop_type() {
let v = sorted_stateful_fixture();
let normal_width =
CssPropertyCache::find_in_stateful(&v, PseudoStateType::Normal, &CssPropertyType::Width)
.expect("normal width present");
assert_eq!(normal_width.get_type(), CssPropertyType::Width);
let hover_width =
CssPropertyCache::find_in_stateful(&v, PseudoStateType::Hover, &CssPropertyType::Width)
.expect("hover width present");
assert_ne!(normal_width, hover_width);
assert!(CssPropertyCache::find_in_stateful(
&v,
PseudoStateType::Focus,
&CssPropertyType::Width
)
.is_none());
assert!(CssPropertyCache::find_in_stateful(
&v,
PseudoStateType::Hover,
&CssPropertyType::Display
)
.is_none());
}
#[test]
fn has_state_props_true_false_and_edges() {
let v = sorted_stateful_fixture();
assert!(CssPropertyCache::has_state_props(&v, PseudoStateType::Normal));
assert!(CssPropertyCache::has_state_props(&v, PseudoStateType::Hover));
assert!(!CssPropertyCache::has_state_props(&v, PseudoStateType::Focus));
assert!(!CssPropertyCache::has_state_props(
&[],
PseudoStateType::Normal
));
}
#[test]
fn prop_types_for_state_filters_by_state() {
let v = sorted_stateful_fixture();
let mut normal: Vec<CssPropertyType> =
CssPropertyCache::prop_types_for_state(&v, PseudoStateType::Normal)
.copied()
.collect();
normal.sort_unstable();
assert_eq!(normal.len(), 2);
assert!(normal.contains(&CssPropertyType::Width));
assert!(normal.contains(&CssPropertyType::Display));
let hover: Vec<CssPropertyType> =
CssPropertyCache::prop_types_for_state(&v, PseudoStateType::Hover)
.copied()
.collect();
assert_eq!(hover, vec![CssPropertyType::Width]);
assert_eq!(
CssPropertyCache::prop_types_for_state(&v, PseudoStateType::Active).count(),
0
);
assert_eq!(
CssPropertyCache::prop_types_for_state(&[], PseudoStateType::Normal).count(),
0
);
}
#[test]
fn resolve_font_size_to_pixels_converts_absolute_units() {
let px = CssPropertyCache::resolve_font_size_to_pixels(&font_size(PixelValue::px(20.0)), 10.0);
let (metric, n) = font_size_parts(&px).unwrap();
assert_eq!(metric, SizeMetric::Px);
assert!(close(n, 20.0));
let pt = CssPropertyCache::resolve_font_size_to_pixels(&font_size(PixelValue::pt(12.0)), 10.0);
assert!(close(font_size_parts(&pt).unwrap().1, 12.0 * PT_TO_PX));
}
#[test]
fn resolve_font_size_to_pixels_em_scales_by_reference_but_rem_does_not() {
let em =
CssPropertyCache::resolve_font_size_to_pixels(&font_size(PixelValue::em(2.0)), 10.0);
assert!(close(font_size_parts(&em).unwrap().1, 20.0));
let rem =
CssPropertyCache::resolve_font_size_to_pixels(&font_size(PixelValue::rem(2.0)), 10.0);
assert!(close(font_size_parts(&rem).unwrap().1, 32.0));
let pct = CssPropertyCache::resolve_font_size_to_pixels(
&font_size(PixelValue::percent(50.0)),
10.0,
);
assert!(close(font_size_parts(&pct).unwrap().1, 5.0));
}
#[test]
fn resolve_font_size_to_pixels_with_nan_and_infinite_references() {
let nan =
CssPropertyCache::resolve_font_size_to_pixels(&font_size(PixelValue::em(2.0)), f32::NAN);
let (metric, n) = font_size_parts(&nan).unwrap();
assert_eq!(metric, SizeMetric::Px);
assert_eq!(n, 0.0, "NaN must not escape into the cascade");
let inf = CssPropertyCache::resolve_font_size_to_pixels(
&font_size(PixelValue::em(2.0)),
f32::INFINITY,
);
let n = font_size_parts(&inf).unwrap().1;
assert!(n.is_finite() && n > 0.0, "saturated, got {n}");
let zero =
CssPropertyCache::resolve_font_size_to_pixels(&font_size(PixelValue::em(2.0)), 0.0);
assert_eq!(font_size_parts(&zero).unwrap().1, 0.0);
let neg =
CssPropertyCache::resolve_font_size_to_pixels(&font_size(PixelValue::em(2.0)), -10.0);
assert!(close(font_size_parts(&neg).unwrap().1, -20.0));
}
#[test]
fn resolve_font_size_to_pixels_passes_through_unresolvable_inputs() {
let vw = font_size(PixelValue::from_metric(SizeMetric::Vw, 10.0));
assert_eq!(CssPropertyCache::resolve_font_size_to_pixels(&vw, 16.0), vw);
let w = width_px(10.0);
assert_eq!(CssPropertyCache::resolve_font_size_to_pixels(&w, 16.0), w);
let inherit = CssProperty::FontSize(CssPropertyValue::Inherit);
assert_eq!(
CssPropertyCache::resolve_font_size_to_pixels(&inherit, 16.0),
inherit
);
}
#[test]
fn has_relative_font_size_unit_true_false_and_edges() {
assert!(CssPropertyCache::has_relative_font_size_unit(&font_size(
PixelValue::em(1.0)
)));
assert!(CssPropertyCache::has_relative_font_size_unit(&font_size(
PixelValue::rem(1.0)
)));
assert!(CssPropertyCache::has_relative_font_size_unit(&font_size(
PixelValue::percent(100.0)
)));
assert!(!CssPropertyCache::has_relative_font_size_unit(&font_size(
PixelValue::px(16.0)
)));
assert!(!CssPropertyCache::has_relative_font_size_unit(&font_size(
PixelValue::pt(12.0)
)));
assert!(!CssPropertyCache::has_relative_font_size_unit(
&CssProperty::FontSize(CssPropertyValue::Auto)
));
assert!(!CssPropertyCache::has_relative_font_size_unit(&width_px(1.0)));
}
#[test]
fn resolve_property_dependency_scales_relative_targets_by_an_absolute_reference() {
let reference = font_size(PixelValue::px(10.0));
let em = CssPropertyCache::resolve_property_dependency(
&font_size(PixelValue::em(2.0)),
&reference,
)
.expect("em resolves against an absolute reference");
assert!(close(font_size_parts(&em).unwrap().1, 20.0));
let pct = CssPropertyCache::resolve_property_dependency(
&font_size(PixelValue::percent(50.0)),
&reference,
)
.expect("percent resolves");
assert!(close(font_size_parts(&pct).unwrap().1, 5.0));
let pt_ref = font_size(PixelValue::pt(10.0));
let em2 =
CssPropertyCache::resolve_property_dependency(&font_size(PixelValue::em(2.0)), &pt_ref)
.expect("pt reference is absolute");
assert!(close(
font_size_parts(&em2).unwrap().1,
2.0 * 10.0 * PT_TO_PX
));
}
#[test]
fn resolve_property_dependency_rewrites_the_target_variant_in_place() {
let reference = font_size(PixelValue::px(10.0));
let padding = CssProperty::PaddingLeft(CssPropertyValue::Exact(LayoutPaddingLeft {
inner: PixelValue::em(3.0),
}));
let out = CssPropertyCache::resolve_property_dependency(&padding, &reference)
.expect("padding is a supported target");
match out {
CssProperty::PaddingLeft(v) => {
let inner = v.get_property().unwrap().inner;
assert_eq!(inner.metric, SizeMetric::Px);
assert!(close(inner.number.get(), 30.0));
}
other => panic!("variant must be preserved, got {other:?}"),
}
}
#[test]
fn resolve_property_dependency_returns_none_for_unresolvable_inputs() {
let abs = font_size(PixelValue::px(10.0));
assert!(CssPropertyCache::resolve_property_dependency(
&font_size(PixelValue::em(2.0)),
&font_size(PixelValue::em(2.0))
)
.is_none());
assert!(CssPropertyCache::resolve_property_dependency(
&font_size(PixelValue::from_metric(SizeMetric::Vh, 5.0)),
&abs
)
.is_none());
assert!(CssPropertyCache::resolve_property_dependency(&width_px(5.0), &abs).is_none());
assert!(CssPropertyCache::resolve_property_dependency(
&font_size(PixelValue::em(2.0)),
&width_px(5.0)
)
.is_none());
assert!(CssPropertyCache::resolve_property_dependency(
&CssProperty::FontSize(CssPropertyValue::Inherit),
&abs
)
.is_none());
}
#[test]
fn should_apply_cascaded_respects_origin_and_relative_font_sizes() {
let own = |p: CssProperty| {
vec![(
p.get_type(),
CssPropertyWithOrigin {
property: p,
origin: CssPropertyOrigin::Own,
},
)]
};
let inherited = |p: CssProperty| {
vec![(
p.get_type(),
CssPropertyWithOrigin {
property: p,
origin: CssPropertyOrigin::Inherited,
},
)]
};
assert!(CssPropertyCache::should_apply_cascaded(
&[],
CssPropertyType::Width,
&width_px(1.0)
));
assert!(!CssPropertyCache::should_apply_cascaded(
&own(width_px(2.0)),
CssPropertyType::Width,
&width_px(1.0)
));
assert!(CssPropertyCache::should_apply_cascaded(
&inherited(width_px(2.0)),
CssPropertyType::Width,
&width_px(1.0)
));
let inherited_fs = inherited(font_size(PixelValue::px(20.0)));
assert!(CssPropertyCache::should_apply_cascaded(
&inherited_fs,
CssPropertyType::FontSize,
&font_size(PixelValue::em(2.0))
));
assert!(CssPropertyCache::should_apply_cascaded(
&inherited_fs,
CssPropertyType::FontSize,
&font_size(PixelValue::px(12.0))
));
}
#[test]
fn get_property_finds_an_inline_normal_property() {
let c = CssPropertyCache::empty(1);
let nd = div_with(vec![width_px(100.0)]);
let got = c
.get_property(&nd, &n0(), &normal(), &CssPropertyType::Width)
.expect("inline width");
assert_eq!(*got, width_px(100.0));
}
#[test]
fn get_property_ignores_pseudo_state_props_unless_the_state_is_active() {
let c = CssPropertyCache::empty(1);
let nd = div_with_pseudo(vec![width_px(100.0)], PseudoStateType::Hover);
assert!(
c.get_property(&nd, &n0(), &normal(), &CssPropertyType::Width)
.is_none(),
":hover width must not leak into the Normal state"
);
let hovered = StyledNodeState {
hover: true,
..StyledNodeState::default()
};
assert_eq!(
c.get_property(&nd, &n0(), &hovered, &CssPropertyType::Width),
Some(&width_px(100.0))
);
}
#[test]
fn get_property_user_override_beats_inline_and_stylesheet() {
let mut c = CssPropertyCache::empty(1);
c.user_overridden_properties
.push(vec![(CssPropertyType::Width, width_px(1.0))]);
c.css_props
.push_to(0, stateful(PseudoStateType::Normal, width_px(2.0)));
c.css_props.sort_each_and_flatten(|p| (p.state, p.prop_type));
let nd = div_with(vec![width_px(3.0)]);
assert_eq!(
c.get_property(&nd, &n0(), &normal(), &CssPropertyType::Width),
Some(&width_px(1.0)),
"user override is the top cascade layer"
);
}
#[test]
fn get_property_falls_back_through_stylesheet_global_cascaded_then_ua() {
let nd = NodeData::create_div();
let mut c = CssPropertyCache::empty(1);
c.css_props
.push_to(0, stateful(PseudoStateType::Normal, width_px(2.0)));
c.css_props.sort_each_and_flatten(|p| (p.state, p.prop_type));
assert_eq!(
c.get_property(&nd, &n0(), &normal(), &CssPropertyType::Width),
Some(&width_px(2.0))
);
let mut c = CssPropertyCache::empty(1);
c.global_css_props.push(width_px(4.0));
assert_eq!(
c.get_property(&nd, &n0(), &normal(), &CssPropertyType::Width),
Some(&width_px(4.0))
);
let mut c = CssPropertyCache::empty(1);
c.cascaded_props
.push_to(0, stateful(PseudoStateType::Normal, width_px(5.0)));
c.cascaded_props
.sort_each_and_flatten(|p| (p.state, p.prop_type));
assert_eq!(
c.get_property(&nd, &n0(), &normal(), &CssPropertyType::Width),
Some(&width_px(5.0))
);
let c = CssPropertyCache::empty(1);
assert!(c
.get_property(&nd, &n0(), &normal(), &CssPropertyType::Width)
.is_none());
assert!(c
.get_property(&nd, &n0(), &normal(), &CssPropertyType::Display)
.is_some());
}
#[test]
fn get_property_on_an_out_of_range_node_id_falls_through_to_ua_css() {
let c = CssPropertyCache::empty(0);
let nd = NodeData::create_div();
let far = NodeId::new(usize::MAX / 2);
assert!(c
.get_property(&nd, &far, &normal(), &CssPropertyType::Width)
.is_none());
assert!(
c.get_property(&nd, &far, &normal(), &CssPropertyType::Display)
.is_some(),
"UA CSS is node-type-keyed, not index-keyed"
);
}
#[test]
fn get_property_with_context_matches_pseudo_state_conditions() {
let c = CssPropertyCache::empty(1);
let nd = div_with_pseudo(vec![width_px(100.0)], PseudoStateType::Hover);
let plain = DynamicSelectorContext::default();
assert!(c
.get_property_with_context(&nd, &n0(), &plain, &CssPropertyType::Width)
.is_none());
let mut hovered = DynamicSelectorContext::default();
hovered.pseudo_state.hover = true;
assert_eq!(
c.get_property_with_context(&nd, &n0(), &hovered, &CssPropertyType::Width),
Some(&width_px(100.0))
);
}
#[test]
fn check_properties_changed_only_fires_when_a_condition_flips() {
let plain = DynamicSelectorContext::default();
let mut hovered = DynamicSelectorContext::default();
hovered.pseudo_state.hover = true;
let unconditional = div_with(vec![width_px(1.0)]);
assert!(!CssPropertyCache::check_properties_changed(
&unconditional,
&plain,
&hovered
));
let conditional = div_with_pseudo(vec![width_px(1.0)], PseudoStateType::Hover);
assert!(CssPropertyCache::check_properties_changed(
&conditional,
&plain,
&hovered
));
assert!(
!CssPropertyCache::check_properties_changed(&conditional, &plain, &plain),
"identical contexts can never differ"
);
assert!(!CssPropertyCache::check_properties_changed(
&NodeData::create_div(),
&plain,
&hovered
));
}
#[test]
fn check_layout_properties_changed_ignores_non_layout_properties() {
let plain = DynamicSelectorContext::default();
let mut hovered = DynamicSelectorContext::default();
hovered.pseudo_state.hover = true;
let layout = div_with_pseudo(vec![width_px(1.0)], PseudoStateType::Hover);
assert!(CssPropertyCache::check_layout_properties_changed(
&layout, &plain, &hovered
));
assert!(CssPropertyType::Width.can_trigger_relayout());
let paint = div_with_pseudo(
vec![CssProperty::const_none(CssPropertyType::BackgroundContent)],
PseudoStateType::Hover,
);
assert!(!CssPropertyType::BackgroundContent.can_trigger_relayout());
assert!(!CssPropertyCache::check_layout_properties_changed(
&paint, &plain, &hovered
));
assert!(CssPropertyCache::check_properties_changed(
&paint, &plain, &hovered
));
}
#[test]
fn grid_gap_and_scrollbar_getters_are_none_on_a_bare_div() {
let c = CssPropertyCache::empty(1);
let nd = NodeData::create_div();
assert!(c.get_grid_gap(&nd, &n0(), &normal()).is_none());
assert!(c.get_scrollbar_track(&nd, &n0(), &normal()).is_none());
assert!(c.get_scrollbar_thumb(&nd, &n0(), &normal()).is_none());
assert!(c.get_scrollbar_button(&nd, &n0(), &normal()).is_none());
assert!(c.get_scrollbar_corner(&nd, &n0(), &normal()).is_none());
assert!(c.get_scrollbar_resizer(&nd, &n0(), &normal()).is_none());
let far = NodeId::new(4_242);
assert!(c.get_grid_gap(&nd, &far, &normal()).is_none());
assert!(c.get_scrollbar_thumb(&nd, &far, &normal()).is_none());
}
#[test]
fn computed_css_style_string_serializes_set_properties() {
let c = CssPropertyCache::empty(1);
let s = c.get_computed_css_style_string(&NodeData::create_div(), &n0(), &normal());
assert!(s.contains("display:"), "got {s:?}");
let styled = div_with(vec![width_px(100.0), font_size(PixelValue::px(12.0))]);
let s = c.get_computed_css_style_string(&styled, &n0(), &normal());
assert!(s.contains("width:"), "got {s:?}");
assert!(s.contains("font-size:"), "got {s:?}");
assert!(s.ends_with(';'), "each declaration is terminated: {s:?}");
}
#[test]
fn computed_css_style_string_does_not_panic_on_an_out_of_range_node_id() {
let c = CssPropertyCache::empty(0);
let s = c.get_computed_css_style_string(
&NodeData::create_div(),
&NodeId::new(usize::MAX / 2),
&normal(),
);
assert!(s.contains("display:"));
}
#[test]
fn apply_ua_css_inserts_ua_properties_into_cascaded_props() {
let nodes = vec![NodeData::create_div()];
let mut c = CssPropertyCache::empty(1);
c.apply_ua_css(&nodes);
let props = c.cascaded_props.build_get(0).expect("build phase");
assert!(
props
.iter()
.any(|p| p.prop_type == CssPropertyType::Display
&& p.state == PseudoStateType::Normal),
"UA `div {{ display: block }}` must land in the cascade"
);
}
#[test]
fn apply_ua_css_does_not_override_an_existing_inline_property() {
let nodes = vec![div_with(vec![CssProperty::const_none(
CssPropertyType::Display,
)])];
let mut c = CssPropertyCache::empty(1);
c.apply_ua_css(&nodes);
let props = c.cascaded_props.build_get(0).expect("build phase");
assert!(
!props.iter().any(|p| p.prop_type == CssPropertyType::Display),
"UA CSS is the weakest layer and must not clobber inline"
);
}
#[test]
fn apply_ua_css_on_zero_nodes_returns_early() {
let mut c = CssPropertyCache::empty(0);
c.apply_ua_css(&[]);
assert_eq!(c.cascaded_props.len(), 0);
}
#[test]
fn sort_cascaded_props_flattens_and_orders_by_state_then_type() {
let mut c = CssPropertyCache::empty(1);
c.cascaded_props
.push_to(0, stateful(PseudoStateType::Hover, width_px(1.0)));
c.cascaded_props.push_to(
0,
stateful(
PseudoStateType::Normal,
CssProperty::const_none(CssPropertyType::Display),
),
);
c.cascaded_props
.push_to(0, stateful(PseudoStateType::Normal, width_px(2.0)));
c.sort_cascaded_props();
assert!(c.cascaded_props.is_flattened());
let slice = c.cascaded_props.get_slice(0);
assert_eq!(slice.len(), 3);
let keys: Vec<_> = slice.iter().map(|p| (p.state, p.prop_type)).collect();
let mut sorted = keys.clone();
sorted.sort_unstable();
assert_eq!(keys, sorted, "binary_search lookups require sort order");
}
#[test]
fn prune_compact_normal_props_keeps_what_the_slow_path_still_needs() {
let mut c = CssPropertyCache::empty(1);
c.cascaded_props.push_to(
0,
stateful(
PseudoStateType::Normal,
CssProperty::const_none(CssPropertyType::Display),
),
);
c.cascaded_props
.push_to(0, stateful(PseudoStateType::Normal, width_pct(50.0)));
c.cascaded_props.push_to(
0,
stateful(
PseudoStateType::Normal,
CssProperty::const_none(CssPropertyType::BackgroundContent),
),
);
c.cascaded_props.push_to(
0,
stateful(
PseudoStateType::Hover,
CssProperty::const_none(CssPropertyType::Display),
),
);
c.prune_compact_normal_props();
let kept: Vec<(PseudoStateType, CssPropertyType)> = c
.cascaded_props
.get_slice(0)
.iter()
.map(|p| (p.state, p.prop_type))
.collect();
assert!(
!kept.contains(&(PseudoStateType::Normal, CssPropertyType::Display)),
"the compact cache is authoritative for this one"
);
assert!(kept.contains(&(PseudoStateType::Normal, CssPropertyType::Width)));
assert!(kept.contains(&(PseudoStateType::Normal, CssPropertyType::BackgroundContent)));
assert!(kept.contains(&(PseudoStateType::Hover, CssPropertyType::Display)));
assert_eq!(kept.len(), 3);
}
#[test]
fn prune_compact_normal_props_on_an_empty_cache_does_not_panic() {
let mut c = CssPropertyCache::empty(0);
c.prune_compact_normal_props();
assert_eq!(c.cascaded_props.len(), 0);
let mut c = CssPropertyCache::empty(3);
c.prune_compact_normal_props();
assert_eq!(c.cascaded_props.len(), 3);
assert!(c.cascaded_props.get_slice(0).is_empty());
}
fn two_node_hierarchy() -> Vec<NodeHierarchyItem> {
vec![
NodeHierarchyItem {
parent: 0,
previous_sibling: 0,
next_sibling: 0,
last_child: 2,
},
NodeHierarchyItem {
parent: 1,
previous_sibling: 0,
next_sibling: 0,
last_child: 0,
},
]
}
#[test]
fn compute_inherited_values_propagates_font_size_to_children() {
let hierarchy = two_node_hierarchy();
assert_eq!(hierarchy[1].parent_id(), Some(NodeId::new(0)));
let nodes = vec![
div_with(vec![font_size(PixelValue::px(20.0))]),
NodeData::create_div(),
];
let mut c = CssPropertyCache::empty(2);
let changed = c.compute_inherited_values(&hierarchy, &nodes);
assert_eq!(c.computed_values.len(), 2);
assert_eq!(changed.len(), 2, "both nodes gained a computed value");
let (t, v) = &c.computed_values[1][0];
assert_eq!(*t, CssPropertyType::FontSize);
assert_eq!(v.origin, CssPropertyOrigin::Inherited);
assert!(close(font_size_parts(&v.property).unwrap().1, 20.0));
assert_eq!(c.computed_values[0][0].1.origin, CssPropertyOrigin::Own);
}
#[test]
fn compute_inherited_values_resolves_a_child_em_against_the_parent_px() {
let hierarchy = two_node_hierarchy();
let nodes = vec![
div_with(vec![font_size(PixelValue::px(20.0))]),
div_with(vec![font_size(PixelValue::em(2.0))]),
];
let mut c = CssPropertyCache::empty(2);
c.compute_inherited_values(&hierarchy, &nodes);
let (t, v) = &c.computed_values[1][0];
assert_eq!(*t, CssPropertyType::FontSize);
assert_eq!(v.origin, CssPropertyOrigin::Own);
let (metric, n) = font_size_parts(&v.property).unwrap();
assert_eq!(metric, SizeMetric::Px, "resolved to absolute px");
assert!(close(n, 40.0), "2em of the parent's 20px, got {n}");
}
#[test]
fn compute_inherited_values_is_idempotent_on_a_second_run() {
let hierarchy = two_node_hierarchy();
let nodes = vec![
div_with(vec![font_size(PixelValue::px(20.0))]),
NodeData::create_div(),
];
let mut c = CssPropertyCache::empty(2);
assert_eq!(c.compute_inherited_values(&hierarchy, &nodes).len(), 2);
assert!(
c.compute_inherited_values(&hierarchy, &nodes).is_empty(),
"nothing changed the second time around"
);
}
#[test]
fn compute_inherited_values_on_an_empty_tree_does_not_panic() {
let mut c = CssPropertyCache::empty(0);
assert!(c.compute_inherited_values(&[], &[]).is_empty());
assert!(c.computed_values.is_empty());
}
fn one_node_scaffold() -> (NodeHierarchyItemVec, NodeDataContainer<CascadeInfo>) {
(
vec![NodeHierarchyItem::zeroed()].into(),
NodeDataContainer::new(vec![CascadeInfo {
index_in_parent: 0,
is_last_child: true,
}]),
)
}
#[test]
fn restyle_with_an_empty_stylesheet_flattens_and_yields_no_tags() {
let (hierarchy, cascade) = one_node_scaffold();
let nodes = NodeDataContainer::new(vec![NodeData::create_div()]);
let non_leaf: ParentWithNodeDepthVec = Vec::new().into();
let mut css = Css::empty();
let mut c = CssPropertyCache::empty(1);
let tags = c.restyle(
&mut css,
&nodes.as_ref(),
&hierarchy,
&non_leaf,
&cascade.as_ref(),
);
assert!(tags.is_empty(), "a plain div needs no hit-test tag");
assert!(
c.css_props.is_flattened(),
"restyle must leave css_props in read phase"
);
assert!(c.resolved_font_sizes_px.get().is_none());
}
#[test]
fn generate_tag_ids_skips_inert_nodes_and_tags_interactive_ones() {
let (hierarchy, _) = one_node_scaffold();
let inert = NodeDataContainer::new(vec![NodeData::create_div()]);
let c = CssPropertyCache::empty(1);
assert!(c.generate_tag_ids(&inert.as_ref(), &hierarchy).is_empty());
let hoverable = NodeDataContainer::new(vec![div_with_pseudo(
vec![width_px(1.0)],
PseudoStateType::Hover,
)]);
let tags = c.generate_tag_ids(&hoverable.as_ref(), &hierarchy);
assert_eq!(tags.len(), 1);
assert_eq!(
tags[0].node_id.into_crate_internal(),
Some(NodeId::new(0))
);
}
#[test]
fn generate_tag_ids_tags_a_node_with_a_cursor_declaration() {
let (hierarchy, _) = one_node_scaffold();
let nodes = NodeDataContainer::new(vec![div_with(vec![CssProperty::const_none(
CssPropertyType::Cursor,
)])]);
let c = CssPropertyCache::empty(1);
assert_eq!(c.generate_tag_ids(&nodes.as_ref(), &hierarchy).len(), 1);
}
#[test]
fn generate_tag_ids_on_an_empty_dom_yields_nothing() {
let nodes: NodeDataContainer<NodeData> = NodeDataContainer::new(Vec::new());
let hierarchy: NodeHierarchyItemVec = Vec::new().into();
let c = CssPropertyCache::empty(0);
assert!(c.generate_tag_ids(&nodes.as_ref(), &hierarchy).is_empty());
}
#[cfg(feature = "std")]
#[test]
fn css_prop_type_label_is_interned_and_distinct_per_variant() {
let a = CssPropertyCache::css_prop_type_label(&CssPropertyType::Width);
let b = CssPropertyCache::css_prop_type_label(&CssPropertyType::Width);
assert!(!a.is_empty());
assert_eq!(
a.as_ptr(),
b.as_ptr(),
"the label table must leak at most one &'static str per variant"
);
let other = CssPropertyCache::css_prop_type_label(&CssPropertyType::Height);
assert_ne!(a, other);
}
#[cfg(feature = "std")]
#[test]
fn drain_css_prop_counts_is_sorted_descending_and_drains() {
let first = drain_css_prop_counts();
for w in first.windows(2) {
assert!(w[0].1 >= w[1].1, "counts must be sorted descending");
}
assert!(
drain_css_prop_counts().is_empty(),
"a drained counter comes back empty"
);
}
}