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);