diff --git a/examples/assets/pseudo.html b/examples/assets/pseudo.html new file mode 100644 index 00000000..a76bc750 --- /dev/null +++ b/examples/assets/pseudo.html @@ -0,0 +1,82 @@ + + + + + + + + +
Hello
+ +
Hello
+ +
  
+ + \ No newline at end of file diff --git a/packages/blitz-dom/src/debug.rs b/packages/blitz-dom/src/debug.rs index a9811c9a..2524c69e 100644 --- a/packages/blitz-dom/src/debug.rs +++ b/packages/blitz-dom/src/debug.rs @@ -83,16 +83,15 @@ impl Document { println!("Layout Parent: {:?}", node.layout_parent.get()); - let layout_children: Vec<_> = node - .layout_children - .borrow() - .as_ref() - .unwrap() - .iter() - .map(|id| &self.nodes[*id]) - .map(|node| (node.id, node.order(), node.node_debug_str())) - .collect(); - println!("Layout Children: {:?}", layout_children); + let layout_children: Option> = node.layout_children.borrow().as_ref().map(|lc| { + lc.iter() + .map(|id| &self.nodes[*id]) + .map(|node| (node.id, node.order(), node.node_debug_str())) + .collect() + }); + if let Some(layout_children) = layout_children { + println!("Layout Children: {:?}", layout_children); + } // taffy::print_tree(&self.dom, node_id.into()); } } diff --git a/packages/blitz-dom/src/document.rs b/packages/blitz-dom/src/document.rs index f299aa11..a1db3a02 100644 --- a/packages/blitz-dom/src/document.rs +++ b/packages/blitz-dom/src/document.rs @@ -861,6 +861,50 @@ impl Document { self.root_element().hit(x, y) } + pub fn iter_children_mut(&mut self, node_id: usize, mut cb: impl FnMut(usize, &mut Document)) { + let children = std::mem::take(&mut self.nodes[node_id].children); + for child_id in children.iter().cloned() { + cb(child_id, self); + } + self.nodes[node_id].children = children; + } + + pub fn iter_subtree_mut(&mut self, node_id: usize, mut cb: impl FnMut(usize, &mut Document)) { + iter_subtree_mut_inner(self, node_id, &mut cb); + fn iter_subtree_mut_inner( + doc: &mut Document, + node_id: usize, + cb: &mut impl FnMut(usize, &mut Document), + ) { + let children = std::mem::take(&mut doc.nodes[node_id].children); + for child_id in children.iter().cloned() { + cb(child_id, doc); + iter_subtree_mut_inner(doc, child_id, cb); + } + doc.nodes[node_id].children = children; + } + } + + pub fn iter_children_and_pseudos_mut( + &mut self, + node_id: usize, + mut cb: impl FnMut(usize, &mut Document), + ) { + let before = self.nodes[node_id].before.take(); + if let Some(before_node_id) = before { + cb(before_node_id, self) + } + self.nodes[node_id].before = before; + + self.iter_children_mut(node_id, &mut cb); + + let after = self.nodes[node_id].after.take(); + if let Some(after_node_id) = after { + cb(after_node_id, self) + } + self.nodes[node_id].after = after; + } + pub fn next_node(&self, start: &Node, mut filter: impl FnMut(&Node) -> bool) -> Option { let start_id = start.id; let mut node = start; diff --git a/packages/blitz-dom/src/layout/construct.rs b/packages/blitz-dom/src/layout/construct.rs index 48edbad4..cab14e0d 100644 --- a/packages/blitz-dom/src/layout/construct.rs +++ b/packages/blitz-dom/src/layout/construct.rs @@ -12,7 +12,7 @@ use style::{ }, shared_lock::StylesheetGuards, values::{ - computed::Display, + computed::{Content, ContentItem, Display}, specified::box_::{DisplayInside, DisplayOutside}, }, }; @@ -27,12 +27,20 @@ use crate::{ use super::table::build_table_context; +const DUMMY_NAME: QualName = QualName { + prefix: None, + ns: ns!(html), + local: local_name!("div"), +}; + pub(crate) fn collect_layout_children( doc: &mut Document, container_node_id: usize, layout_children: &mut Vec, anonymous_block_id: &mut Option, ) { + flush_pseudo_elements(doc, container_node_id); + if let Some(el) = doc.nodes[container_node_id].raw_dom_data.downcast_element() { // Handle text inputs let tag_name = el.name.local.as_ref(); @@ -204,7 +212,75 @@ pub(crate) fn collect_layout_children( } _ => { + if let Some(before) = doc.nodes[container_node_id].before { + layout_children.push(before); + } layout_children.extend_from_slice(&doc.nodes[container_node_id].children); + if let Some(after) = doc.nodes[container_node_id].after { + layout_children.push(after); + } + } + } +} + +fn flush_pseudo_elements(doc: &mut Document, node_id: usize) { + let (before_style, after_style, before_node_id, after_node_id) = { + let node = &doc.nodes[node_id]; + + let before_node_id = node.before; + let after_node_id = node.after; + + // Note: yes these are kinda backwards + let style_data = node.stylo_element_data.borrow(); + let before_style = style_data + .as_ref() + .and_then(|d| d.styles.pseudos.as_array()[1].clone()); + let after_style = style_data + .as_ref() + .and_then(|d| d.styles.pseudos.as_array()[0].clone()); + + (before_style, after_style, before_node_id, after_node_id) + }; + + // Sync pseudo element + // TODO: Make incremental + for (idx, pe_style, pe_node_id) in [ + (1, before_style, before_node_id), + (0, after_style, after_node_id), + ] { + // Delete psuedo element if it exists + if let Some(pe_node_id) = pe_node_id { + doc.remove_and_drop_node(pe_node_id); + doc.nodes[node_id].set_pe_by_index(idx, None); + } + + // (Re)create pseudo element if it should exist + if let Some(pe_style) = pe_style { + let new_node_id = doc.create_node(NodeData::AnonymousBlock(ElementNodeData::new( + DUMMY_NAME, + Vec::new(), + ))); + + let content = &pe_style.as_ref().get_counters().content; + if let Content::Items(item_data) = content { + let items = &item_data.items[0..item_data.alt_start]; + match &items[0] { + ContentItem::String(owned_str) => { + let text_node_id = doc.create_text_node(owned_str); + doc.nodes[new_node_id].children.push(text_node_id); + } + _ => { + // TODO: other types of content + } + } + } + + let mut element_data = ElementData::default(); + element_data.styles.primary = Some(pe_style); + element_data.set_restyled(); + *doc.nodes[new_node_id].stylo_element_data.borrow_mut() = Some(element_data); + + doc.nodes[node_id].set_pe_by_index(idx, Some(new_node_id)); } } } @@ -399,10 +475,7 @@ fn collect_complex_layout_children( hide_whitespace: bool, needs_wrap: impl Fn(NodeKind, DisplayOutside) -> bool, ) { - // Take children array from node to avoid borrow checker issues. - let children = std::mem::take(&mut doc.nodes[container_node_id].children); - - for child_id in children.iter().copied() { + doc.iter_children_and_pseudos_mut(container_node_id, |child_id, doc| { // Get node kind (text, element, comment, etc) let child_node_kind = doc.nodes[child_id].raw_dom_data.kind(); @@ -428,7 +501,7 @@ fn collect_complex_layout_children( // // Also hide all-whitespace flexbox children as these should be ignored if child_node_kind == NodeKind::Comment || (hide_whitespace && is_whitespace_node) { - continue; + // return; } // Recurse into `Display::Contents` nodes else if display_inside == DisplayInside::Contents { @@ -477,10 +550,7 @@ fn collect_complex_layout_children( *anonymous_block_id = None; layout_children.push(child_id); } - } - - // Put children array back - doc.nodes[container_node_id].children = children; + }); } fn create_text_editor(doc: &mut Document, input_element_id: usize, is_multiline: bool) { @@ -532,6 +602,10 @@ pub(crate) fn build_inline_layout( doc: &mut Document, inline_context_root_node_id: usize, ) -> (TextLayout, Vec) { + // println!("Inline context {}", inline_context_root_node_id); + + flush_inline_pseudos_recursive(doc, inline_context_root_node_id); + // Get the inline context's root node's text styles let root_node = &doc.nodes[inline_context_root_node_id]; let root_node_style = root_node.primary_styles().or_else(|| { @@ -545,6 +619,8 @@ pub(crate) fn build_inline_layout( .map(|s| stylo_to_parley::style(s)) .unwrap_or_default(); + // dbg!(&parley_style); + let root_line_height = parley_style.line_height; // Create a parley tree builder @@ -559,7 +635,7 @@ pub(crate) fn build_inline_layout( .unwrap_or(WhiteSpaceCollapse::Collapse); builder.set_white_space_mode(collapse_mode); - //Render position-inside list items + // Render position-inside list items if let Some(ListItemLayout { marker, position: ListItemLayoutPosition::Inside, @@ -573,6 +649,15 @@ pub(crate) fn build_inline_layout( } }; + if let Some(before_id) = root_node.before { + build_inline_layout_recursive( + &mut builder, + &doc.nodes, + before_id, + collapse_mode, + root_line_height, + ); + } for child_id in root_node.children.iter().copied() { build_inline_layout_recursive( &mut builder, @@ -582,6 +667,15 @@ pub(crate) fn build_inline_layout( root_line_height, ); } + if let Some(after_id) = root_node.after { + build_inline_layout_recursive( + &mut builder, + &doc.nodes, + after_id, + collapse_mode, + root_line_height, + ); + } let (layout, text) = builder.build(); @@ -599,6 +693,23 @@ pub(crate) fn build_inline_layout( return (TextLayout { text, layout }, layout_children); + fn flush_inline_pseudos_recursive(doc: &mut Document, node_id: usize) { + doc.iter_children_mut(node_id, |child_id, doc| { + flush_pseudo_elements(doc, child_id); + let display = doc.nodes[node_id] + .display_style() + .unwrap_or(Display::inline()); + let do_recurse = match (display.outside(), display.inside()) { + (DisplayOutside::None, DisplayInside::Contents) => true, + (DisplayOutside::Inline, DisplayInside::Flow) => true, + (_, _) => false, + }; + if do_recurse { + flush_inline_pseudos_recursive(doc, child_id); + } + }); + } + fn build_inline_layout_recursive( builder: &mut TreeBuilder, nodes: &Slab, @@ -669,12 +780,29 @@ pub(crate) fn build_inline_layout( .map(|s| stylo_to_parley::style(&s)) .unwrap_or_default(); + // dbg!(&style); + + // style.brush = peniko::Brush::Solid(peniko::Color::WHITE); + // Floor the line-height of the span by the line-height of the inline context // See https://www.w3.org/TR/CSS21/visudet.html#line-height style.line_height = style.line_height.max(root_line_height); + // dbg!(node_id); + // dbg!(&style); + builder.push_style_span(style); + if let Some(before_id) = node.before { + build_inline_layout_recursive( + builder, + nodes, + before_id, + collapse_mode, + root_line_height, + ); + } + for child_id in node.children.iter().copied() { build_inline_layout_recursive( builder, @@ -684,6 +812,15 @@ pub(crate) fn build_inline_layout( root_line_height, ); } + if let Some(after_id) = node.after { + build_inline_layout_recursive( + builder, + nodes, + after_id, + collapse_mode, + root_line_height, + ); + } builder.pop_style_span(); } @@ -702,6 +839,7 @@ pub(crate) fn build_inline_layout( }; } NodeData::Text(data) => { + // dbg!(&data.content); builder.push_text(&data.content); } NodeData::Comment => {} diff --git a/packages/blitz-dom/src/node.rs b/packages/blitz-dom/src/node.rs index b684509c..b9509982 100644 --- a/packages/blitz-dom/src/node.rs +++ b/packages/blitz-dom/src/node.rs @@ -12,6 +12,7 @@ use std::sync::atomic::AtomicBool; use std::sync::Arc; use style::invalidation::element::restyle_hints::RestyleHint; use style::properties::ComputedValues; +use style::selector_parser::PseudoElement; use style::stylesheets::UrlExtraData; use style::values::computed::Display; use style::values::specified::box_::{DisplayInside, DisplayOutside}; @@ -67,6 +68,10 @@ pub struct Node { pub guard: SharedRwLock, pub element_state: ElementState, + // Pseudo element nodes + pub before: Option, + pub after: Option, + // Taffy layout data: pub style: Style, pub hidden: bool, @@ -102,6 +107,9 @@ impl Node { guard, element_state: ElementState::empty(), + before: None, + after: None, + style: Default::default(), hidden: false, is_hovered: false, @@ -118,6 +126,22 @@ impl Node { } } + pub fn pe_by_index(&self, index: usize) -> Option { + match index { + 0 => self.after, + 1 => self.before, + _ => panic!("Invalid pseudo element index"), + } + } + + pub fn set_pe_by_index(&mut self, index: usize, value: Option) { + match index { + 0 => self.after = value, + 1 => self.before = value, + _ => panic!("Invalid pseudo element index"), + } + } + pub(crate) fn display_style(&self) -> Option { // if self.is_text_node() { // return Some(Display::inline()) @@ -827,7 +851,11 @@ impl Node { .borrow() .as_ref() .and_then(|data| data.styles.get_primary()) - .map(|s| s.clone_order()) + .map(|s| match s.pseudo() { + Some(PseudoElement::Before) => i32::MIN, + Some(PseudoElement::After) => i32::MAX, + _ => s.clone_order(), + }) .unwrap_or(0) } diff --git a/packages/blitz-dom/src/stylo.rs b/packages/blitz-dom/src/stylo.rs index 01c51005..6024222a 100644 --- a/packages/blitz-dom/src/stylo.rs +++ b/packages/blitz-dom/src/stylo.rs @@ -53,7 +53,6 @@ use style::values::computed::text::TextAlign as StyloTextAlign; impl crate::document::Document { /// Walk the whole tree, converting styles to layout pub fn flush_styles_to_layout(&mut self, node_id: usize) { - let display = { let node = self.nodes.get_mut(node_id).unwrap(); let stylo_element_data = node.stylo_element_data.borrow(); @@ -85,7 +84,6 @@ impl crate::document::Document { // If the node has children, then take those children and... let children = self.nodes[node_id].layout_children.borrow_mut().take(); if let Some(mut children) = children { - // Recursively call flush_styles_to_layout on each child for child in children.iter() { self.flush_styles_to_layout(*child); @@ -108,6 +106,17 @@ impl crate::document::Document { pub fn resolve_stylist(&mut self) { style::thread_state::enter(ThreadState::LAYOUT); + self.iter_subtree_mut(self.root_node().id, |node_id, doc| { + doc.snapshot_node(node_id); + let node = &doc.nodes[node_id]; + let mut stylo_element_data = node.stylo_element_data.borrow_mut(); + if let Some(data) = &mut *stylo_element_data { + data.hint |= RestyleHint::restyle_subtree(); + data.hint |= RestyleHint::recascade_subtree(); + data.hint |= RestyleHint::RESTYLE_PSEUDOS; + } + }); + let guard = &self.guard; let guards = StylesheetGuards { author: &guard.read(), @@ -126,6 +135,7 @@ impl crate::document::Document { if let Some(data) = &mut *stylo_element_data { data.hint |= RestyleHint::restyle_subtree(); data.hint |= RestyleHint::recascade_subtree(); + data.hint |= RestyleHint::RESTYLE_PSEUDOS; } drop(stylo_element_data);