use alloc::vec::Vec;
use azul_css::css::{
CssContentGroup, CssNthChildSelector, CssNthChildSelector::*, CssPath, CssPathPseudoSelector,
CssPathSelector,
};
use crate::{
dom::NodeData,
id::{NodeDataContainer, NodeDataContainerRef, NodeHierarchyRef, NodeId},
styled_dom::NodeHierarchyItem,
};
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct CascadeInfo {
pub index_in_parent: u32,
pub is_last_child: bool,
}
impl_option!(
CascadeInfo,
OptionCascadeInfo,
[Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
);
impl_vec!(CascadeInfo, CascadeInfoVec, CascadeInfoVecDestructor, CascadeInfoVecDestructorType, CascadeInfoVecSlice, OptionCascadeInfo);
impl_vec_mut!(CascadeInfo, CascadeInfoVec);
impl_vec_debug!(CascadeInfo, CascadeInfoVec);
impl_vec_partialord!(CascadeInfo, CascadeInfoVec);
impl_vec_clone!(CascadeInfo, CascadeInfoVec, CascadeInfoVecDestructor);
impl_vec_partialeq!(CascadeInfo, CascadeInfoVec);
impl CascadeInfoVec {
pub fn as_container<'a>(&'a self) -> NodeDataContainerRef<'a, CascadeInfo> {
NodeDataContainerRef {
internal: self.as_ref(),
}
}
}
pub fn matches_html_element(
css_path: &CssPath,
node_id: NodeId,
node_hierarchy: &NodeDataContainerRef<NodeHierarchyItem>,
node_data: &NodeDataContainerRef<NodeData>,
html_node_tree: &NodeDataContainerRef<CascadeInfo>,
expected_path_ending: Option<CssPathPseudoSelector>,
) -> bool {
use self::CssGroupSplitReason::*;
if css_path.selectors.is_empty() {
return false;
}
if node_data[node_id].is_anonymous() {
return false;
}
let mut current_node = Some(node_id);
let mut next_match_requirement = Children; let mut last_selector_matched = true;
let mut iterator = CssGroupIterator::new(css_path.selectors.as_ref());
while let Some((content_group, reason)) = iterator.next() {
let is_last_content_group = iterator.is_last_content_group();
let cur_node_id = match current_node {
Some(c) => c,
None => {
return *content_group == [&CssPathSelector::Global];
}
};
let current_selector_matches = selector_group_matches(
&content_group,
&html_node_tree[cur_node_id],
&node_data[cur_node_id],
expected_path_ending.clone(),
is_last_content_group,
);
match next_match_requirement {
DirectChildren => {
if !current_selector_matches {
return false;
}
}
AdjacentSibling => {
if !current_selector_matches {
return false;
}
}
GeneralSibling => {
if !current_selector_matches {
let mut found_match = false;
let mut sibling = node_hierarchy[cur_node_id].previous_sibling_id();
while let Some(sib_id) = sibling {
if selector_group_matches(
&content_group,
&html_node_tree[sib_id],
&node_data[sib_id],
expected_path_ending.clone(),
is_last_content_group,
) {
found_match = true;
current_node = Some(sib_id);
break;
}
sibling = node_hierarchy[sib_id].previous_sibling_id();
}
if !found_match {
return false;
}
next_match_requirement = reason;
continue;
}
}
Children => {
if current_selector_matches && !last_selector_matched {
return false;
}
}
}
last_selector_matched = current_selector_matches;
next_match_requirement = reason;
match reason {
Children | DirectChildren => {
let mut next = node_hierarchy[cur_node_id].parent_id();
while let Some(n) = next {
if !node_data[n].is_anonymous() {
break;
}
next = node_hierarchy[n].parent_id();
}
current_node = next;
}
AdjacentSibling | GeneralSibling => {
let mut next = node_hierarchy[cur_node_id].previous_sibling_id();
while let Some(n) = next {
if !node_data[n].is_anonymous() {
break;
}
next = node_hierarchy[n].previous_sibling_id();
}
current_node = next;
}
}
}
last_selector_matched
}
pub struct CssGroupIterator<'a> {
pub css_path: &'a [CssPathSelector],
pub current_idx: usize,
pub last_reason: CssGroupSplitReason,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum CssGroupSplitReason {
Children,
DirectChildren,
AdjacentSibling,
GeneralSibling,
}
impl<'a> CssGroupIterator<'a> {
pub fn new(css_path: &'a [CssPathSelector]) -> Self {
let initial_len = css_path.len();
Self {
css_path,
current_idx: initial_len,
last_reason: CssGroupSplitReason::Children,
}
}
pub fn is_last_content_group(&self) -> bool {
self.current_idx.saturating_add(1) == self.css_path.len().saturating_sub(1)
}
}
impl<'a> Iterator for CssGroupIterator<'a> {
type Item = (CssContentGroup<'a>, CssGroupSplitReason);
fn next(&mut self) -> Option<(CssContentGroup<'a>, CssGroupSplitReason)> {
use self::CssPathSelector::*;
let mut new_idx = self.current_idx;
if new_idx == 0 {
return None;
}
let mut current_path = Vec::new();
while new_idx != 0 {
match self.css_path.get(new_idx - 1)? {
Children => {
self.last_reason = CssGroupSplitReason::Children;
break;
}
DirectChildren => {
self.last_reason = CssGroupSplitReason::DirectChildren;
break;
}
AdjacentSibling => {
self.last_reason = CssGroupSplitReason::AdjacentSibling;
break;
}
GeneralSibling => {
self.last_reason = CssGroupSplitReason::GeneralSibling;
break;
}
other => current_path.push(other),
}
new_idx -= 1;
}
#[cfg(test)]
current_path.reverse();
if new_idx == 0 {
if current_path.is_empty() {
None
} else {
self.current_idx = 0;
Some((current_path, self.last_reason))
}
} else {
self.current_idx = new_idx - 1;
Some((current_path, self.last_reason))
}
}
}
pub fn construct_html_cascade_tree(
node_hierarchy: &NodeHierarchyRef,
node_depths_sorted: &[(usize, NodeId)],
node_data: &NodeDataContainerRef<NodeData>,
) -> NodeDataContainer<CascadeInfo> {
let mut nodes = (0..node_hierarchy.len())
.map(|_| CascadeInfo {
index_in_parent: 0,
is_last_child: false,
})
.collect::<Vec<_>>();
for (_depth, parent_id) in node_depths_sorted {
let element_index_in_parent = parent_id
.preceding_siblings(node_hierarchy)
.filter(|sib_id| !node_data[*sib_id].is_text_node())
.count();
let parent_html_matcher = CascadeInfo {
index_in_parent: (element_index_in_parent - 1) as u32,
is_last_child: {
let mut is_last_element = true;
let mut next = node_hierarchy[*parent_id].next_sibling;
while let Some(sib_id) = next {
if !node_data[sib_id].is_text_node() {
is_last_element = false;
break;
}
next = node_hierarchy[sib_id].next_sibling;
}
is_last_element
},
};
nodes[parent_id.index()] = parent_html_matcher;
let mut element_idx: u32 = 0;
for child_id in parent_id.children(node_hierarchy) {
let is_text = node_data[child_id].is_text_node();
let is_last_element_child = if is_text {
false
} else {
let mut is_last = true;
let mut next = node_hierarchy[child_id].next_sibling;
while let Some(sib_id) = next {
if !node_data[sib_id].is_text_node() {
is_last = false;
break;
}
next = node_hierarchy[sib_id].next_sibling;
}
is_last
};
let child_html_matcher = CascadeInfo {
index_in_parent: element_idx,
is_last_child: is_last_element_child,
};
nodes[child_id.index()] = child_html_matcher;
if !is_text {
element_idx += 1;
}
}
}
NodeDataContainer { internal: nodes }
}
#[inline]
pub fn rule_ends_with(path: &CssPath, target: Option<CssPathPseudoSelector>) -> bool {
fn is_interactive_pseudo(p: &CssPathPseudoSelector) -> bool {
matches!(
p,
CssPathPseudoSelector::Hover
| CssPathPseudoSelector::Active
| CssPathPseudoSelector::Focus
| CssPathPseudoSelector::Backdrop
| CssPathPseudoSelector::Dragging
| CssPathPseudoSelector::DragOver
)
}
match target {
None => match path.selectors.as_ref().last() {
None => false,
Some(q) => match q {
CssPathSelector::PseudoSelector(p) => !is_interactive_pseudo(p),
_ => true,
},
},
Some(s) => match path.selectors.as_ref().last() {
None => false,
Some(q) => match q {
CssPathSelector::PseudoSelector(q) => *q == s,
_ => false,
},
},
}
}
pub fn selector_group_matches(
selectors: &[&CssPathSelector],
html_node: &CascadeInfo,
node_data: &NodeData,
expected_path_ending: Option<CssPathPseudoSelector>,
is_last_content_group: bool,
) -> bool {
selectors.iter().all(|selector| {
match_single_selector(
selector,
html_node,
node_data,
expected_path_ending.clone(),
is_last_content_group,
)
})
}
fn match_single_selector(
selector: &CssPathSelector,
html_node: &CascadeInfo,
node_data: &NodeData,
expected_path_ending: Option<CssPathPseudoSelector>,
is_last_content_group: bool,
) -> bool {
use self::CssPathSelector::*;
match selector {
Global => true,
Type(t) => node_data.get_node_type().get_path() == *t,
Class(c) => match_class(node_data, c.as_str()),
Id(id) => match_id(node_data, id.as_str()),
PseudoSelector(p) => {
match_pseudo_selector(p, html_node, expected_path_ending, is_last_content_group)
}
DirectChildren | Children | AdjacentSibling | GeneralSibling => false,
}
}
fn match_class(node_data: &NodeData, class_name: &str) -> bool {
node_data
.get_ids_and_classes()
.iter()
.filter_map(|i| i.as_class())
.any(|class| class == class_name)
}
fn match_id(node_data: &NodeData, id_name: &str) -> bool {
node_data
.get_ids_and_classes()
.iter()
.filter_map(|i| i.as_id())
.any(|html_id| html_id == id_name)
}
fn match_pseudo_selector(
pseudo: &CssPathPseudoSelector,
html_node: &CascadeInfo,
expected_path_ending: Option<CssPathPseudoSelector>,
is_last_content_group: bool,
) -> bool {
match pseudo {
CssPathPseudoSelector::First => match_first_child(html_node),
CssPathPseudoSelector::Last => match_last_child(html_node),
CssPathPseudoSelector::NthChild(pattern) => match_nth_child(html_node, pattern),
CssPathPseudoSelector::Hover => match_interactive_pseudo(
CssPathPseudoSelector::Hover,
expected_path_ending,
is_last_content_group,
),
CssPathPseudoSelector::Active => match_interactive_pseudo(
CssPathPseudoSelector::Active,
expected_path_ending,
is_last_content_group,
),
CssPathPseudoSelector::Focus => match_interactive_pseudo(
CssPathPseudoSelector::Focus,
expected_path_ending,
is_last_content_group,
),
CssPathPseudoSelector::Backdrop => match_interactive_pseudo(
CssPathPseudoSelector::Backdrop,
expected_path_ending,
is_last_content_group,
),
CssPathPseudoSelector::Dragging => match_interactive_pseudo(
CssPathPseudoSelector::Dragging,
expected_path_ending,
is_last_content_group,
),
CssPathPseudoSelector::DragOver => match_interactive_pseudo(
CssPathPseudoSelector::DragOver,
expected_path_ending,
is_last_content_group,
),
CssPathPseudoSelector::Lang(lang) => {
if let Some(ref expected) = expected_path_ending {
if let CssPathPseudoSelector::Lang(expected_lang) = expected {
return lang == expected_lang;
}
}
false
}
}
}
fn match_first_child(html_node: &CascadeInfo) -> bool {
html_node.index_in_parent == 0
}
fn match_last_child(html_node: &CascadeInfo) -> bool {
html_node.is_last_child
}
fn match_nth_child(html_node: &CascadeInfo, pattern: &CssNthChildSelector) -> bool {
use azul_css::css::CssNthChildPattern;
let index = html_node.index_in_parent + 1;
match pattern {
Number(n) => index == *n,
Even => index % 2 == 0,
Odd => index % 2 == 1,
Pattern(CssNthChildPattern {
pattern_repeat,
offset,
}) => {
if *pattern_repeat == 0 {
index == *offset
} else {
index >= *offset && ((index - offset) % pattern_repeat == 0)
}
}
}
}
fn match_interactive_pseudo(
pseudo: CssPathPseudoSelector,
expected_path_ending: Option<CssPathPseudoSelector>,
is_last_content_group: bool,
) -> bool {
is_last_content_group && expected_path_ending == Some(pseudo)
}