use std::sync::Arc;
use crate::ScrollHandle;
use crate::collections::{FxHashMap, FxHashSet};
use crate::{
BorderStyle, Bounds, CursorStyle, ElementId, FontStyle, FontWeight, GlobalElementId, Hsla,
Pixels, Point, SharedString, TextAlign, WhiteSpace,
};
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum DomDisplay {
#[default]
Block,
None,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum DomGradientKind {
#[default]
Linear,
Radial,
Conic,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct DomGradient {
pub kind: DomGradientKind,
pub angle: f32,
pub stops: Vec<(Hsla, f32)>,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct DomBoxShadow {
pub color: Hsla,
pub offset_x: Pixels,
pub offset_y: Pixels,
pub blur_radius: Pixels,
pub spread_radius: Pixels,
pub inset: bool,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum DomOverflow {
#[default]
Visible,
Hidden,
Scroll,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum DomPosition {
#[default]
Absolute,
Static,
Relative,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum DomTextDecoration {
#[default]
None,
Underline,
LineThrough,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct DomStyle {
pub display: DomDisplay,
pub position: DomPosition,
pub left: Pixels,
pub top: Pixels,
pub width: Pixels,
pub height: Pixels,
pub color: Option<Hsla>,
pub background_color: Option<Hsla>,
pub background_gradient: Option<DomGradient>,
pub border_radius: Option<Pixels>,
pub border_color: Option<Hsla>,
pub border_width: Option<Pixels>,
pub border_style: Option<BorderStyle>,
pub box_shadows: Vec<DomBoxShadow>,
pub font_size: Option<Pixels>,
pub font_family: Option<SharedString>,
pub font_weight: Option<FontWeight>,
pub font_style: Option<FontStyle>,
pub line_height: Option<Pixels>,
pub text_align: Option<TextAlign>,
pub white_space: Option<WhiteSpace>,
pub text_decoration: DomTextDecoration,
pub overflow: DomOverflow,
pub cursor: Option<CursorStyle>,
pub opacity: Option<f32>,
pub z_index: u32,
}
impl DomStyle {
pub fn from_bounds(bounds: Bounds<Pixels>) -> Self {
Self {
left: bounds.origin.x,
top: bounds.origin.y,
width: bounds.size.width,
height: bounds.size.height,
..Default::default()
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum DomNodeKind {
Element {
tag: &'static str,
attrs: Vec<(String, String)>,
children: Vec<DomNode>,
},
Text {
text: SharedString,
},
}
impl DomNodeKind {
pub fn dom_tag(&self) -> &'static str {
match self {
DomNodeKind::Element { tag, .. } => tag,
DomNodeKind::Text { .. } => "span",
}
}
}
#[derive(Clone, Debug)]
pub struct DomNode {
pub kind: DomNodeKind,
pub style: DomStyle,
pub scroll_handle: Option<ScrollHandle>,
}
impl PartialEq for DomNode {
fn eq(&self, other: &Self) -> bool {
self.kind == other.kind && self.style == other.style
}
}
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
pub struct DomNodeKey {
pub global_id: GlobalElementId,
pub dom_path: Vec<u32>,
}
impl DomNodeKey {
pub fn root() -> Self {
Self {
global_id: GlobalElementId::default(),
dom_path: Vec::new(),
}
}
pub fn is_keyed(&self) -> bool {
self.dom_path.is_empty()
}
pub fn to_dom_id(&self) -> String {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
self.hash(&mut hasher);
hasher.finish().to_string()
}
}
#[derive(Clone, Debug, Default)]
pub struct DomTree {
pub root: DomNodeKey,
pub nodes: FxHashMap<DomNodeKey, DomNode>,
pub children: FxHashMap<DomNodeKey, Vec<DomNodeKey>>,
pub z_orders: FxHashMap<DomNodeKey, u32>,
}
impl DomTree {
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
pub fn for_each_in_paint_order(&self, mut f: impl FnMut(&DomNodeKey, &DomNode, u32)) {
let mut keys: Vec<&DomNodeKey> = self.nodes.keys().collect();
keys.sort_by_key(|key| self.z_orders.get(*key).copied().unwrap_or(0));
for key in keys {
let z = self.z_orders.get(key).copied().unwrap_or(0);
if let Some(node) = self.nodes.get(key) {
f(key, node, z);
}
}
}
}
pub struct DomTreeBuilder {
tree: DomTree,
stack: Vec<DomNodeKey>,
origins: Vec<Point<Pixels>>,
anon_counts: FxHashMap<DomNodeKey, u32>,
order: u32,
seen: FxHashSet<DomNodeKey>,
global_id_to_key: FxHashMap<GlobalElementId, DomNodeKey>,
}
impl Default for DomTreeBuilder {
fn default() -> Self {
Self::new()
}
}
impl DomTreeBuilder {
pub fn new() -> Self {
Self {
tree: DomTree::default(),
stack: Vec::new(),
origins: Vec::new(),
anon_counts: FxHashMap::default(),
order: 0,
seen: FxHashSet::default(),
global_id_to_key: FxHashMap::default(),
}
}
pub fn begin_frame(&mut self) {
self.tree = DomTree::default();
self.tree.root = DomNodeKey::root();
self.stack.clear();
self.origins.clear();
self.anon_counts.clear();
self.seen.clear();
self.global_id_to_key.clear();
self.order = 0;
self.stack.push(self.tree.root.clone());
self.origins.push(Point::default());
}
pub fn register(
&mut self,
node: DomNode,
is_keyed: bool,
element_path: &[ElementId],
) -> DomNodeKey {
let parent = self
.stack
.last()
.expect("dom stack 不能为空,需先 begin_frame")
.clone();
let parent_origin = *self
.origins
.last()
.expect("dom origins 与 stack 平行,不能为空");
let window_origin = Point {
x: node.style.left,
y: node.style.top,
};
let mut node = node;
let internal_children = match &mut node.kind {
DomNodeKind::Element { children, .. } => std::mem::take(children),
_ => Vec::new(),
};
node.style.left = window_origin.x - parent_origin.x;
node.style.top = window_origin.y - parent_origin.y;
let global_id = GlobalElementId(Arc::from(element_path));
let mut key = if is_keyed {
DomNodeKey {
global_id,
dom_path: Vec::new(),
}
} else {
let index = self.anon_counts.entry(parent.clone()).or_insert(0);
*index += 1;
let mut dom_path = parent.dom_path.clone();
dom_path.push(*index);
DomNodeKey {
global_id,
dom_path,
}
};
if self.seen.contains(&key) {
let index = self.anon_counts.entry(parent.clone()).or_insert(0);
*index += 1;
let mut dom_path = parent.dom_path.clone();
dom_path.push(*index);
key = DomNodeKey {
global_id: key.global_id,
dom_path,
};
}
debug_assert!(!self.seen.contains(&key), "DOM key 重复:{}", key.global_id);
self.seen.insert(key.clone());
self.global_id_to_key
.insert(key.global_id.clone(), key.clone());
self.tree.nodes.insert(key.clone(), node);
self.tree
.children
.entry(parent)
.or_default()
.push(key.clone());
self.tree.z_orders.insert(key.clone(), self.order);
self.order += 1;
self.stack.push(key.clone());
self.origins.push(window_origin);
for child in internal_children {
let _ = self.register(child, false, element_path);
self.exit();
}
key
}
pub fn exit(&mut self) {
if self.stack.len() > 1 {
self.stack.pop();
self.origins.pop();
}
}
pub fn current_parent(&self) -> DomNodeKey {
self.stack.last().expect("dom stack 不能为空").clone()
}
pub fn stack_len(&self) -> usize {
self.stack.len()
}
pub fn key_for_element_id_stack(&self, element_id_stack: &[ElementId]) -> Option<DomNodeKey> {
if element_id_stack.is_empty() {
return None;
}
let global_id = GlobalElementId(Arc::from(element_id_stack));
self.global_id_to_key.get(&global_id).cloned()
}
pub fn finish(&mut self) -> DomTree {
let mut tree = std::mem::take(&mut self.tree);
tree.root = DomNodeKey::root();
self.stack.clear();
tree
}
}
thread_local! {
static DOM_LAYER_ENABLED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}
pub fn set_dom_layer_enabled(enabled: bool) {
DOM_LAYER_ENABLED.with(|cell| cell.set(enabled));
}
pub fn dom_layer_enabled() -> bool {
DOM_LAYER_ENABLED.with(|cell| cell.get())
}
thread_local! {
static DOM_FONT_FACES: std::cell::RefCell<Vec<DomFontFace>> =
const { std::cell::RefCell::new(Vec::new()) };
}
#[derive(Clone, Debug)]
pub struct DomFontFace {
pub family: SharedString,
pub data: Arc<Vec<u8>>,
}
pub fn set_dom_font_face(family: impl Into<SharedString>, data: impl AsRef<[u8]>) {
DOM_FONT_FACES.with(|faces| {
faces.borrow_mut().push(DomFontFace {
family: family.into(),
data: Arc::new(data.as_ref().to_vec()),
});
});
}
pub fn dom_font_faces() -> Vec<DomFontFace> {
DOM_FONT_FACES.with(|faces| faces.borrow().clone())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::SharedString;
fn key(path: &[ElementId], dom_path: &[u32]) -> DomNodeKey {
DomNodeKey {
global_id: GlobalElementId(Arc::from(path)),
dom_path: dom_path.to_vec(),
}
}
fn div_node() -> DomNode {
DomNode {
kind: DomNodeKind::Element {
tag: "div",
attrs: Vec::new(),
children: Vec::new(),
},
style: DomStyle::default(),
scroll_handle: None,
}
}
fn text_node(text: &str) -> DomNode {
DomNode {
kind: DomNodeKind::Text {
text: SharedString::from(text),
},
style: DomStyle::default(),
scroll_handle: None,
}
}
#[test]
fn test_builder_hierarchy_and_keys() {
let mut builder = DomTreeBuilder::new();
builder.begin_frame();
let a = ElementId::Name("a".into());
let div_key = builder.register(div_node(), true, &[a.clone()]);
let text1_key = builder.register(text_node("hello"), false, &[a.clone()]);
builder.exit();
let text2_key = builder.register(text_node("world"), false, &[a.clone()]);
builder.exit();
builder.exit();
let tree = builder.finish();
assert_eq!(div_key, key(&[a.clone()], &[]));
assert_eq!(text1_key, key(&[a.clone()], &[1]));
assert_eq!(text2_key, key(&[a.clone()], &[2]));
let children = tree.children.get(&tree.root).unwrap();
assert_eq!(children, &vec![div_key.clone()]);
let div_children = tree.children.get(&div_key).unwrap();
assert_eq!(div_children, &vec![text1_key.clone(), text2_key.clone()]);
assert_eq!(tree.nodes.get(&text1_key).unwrap(), &text_node("hello"));
assert_eq!(tree.z_orders.get(&div_key), Some(&0));
assert_eq!(tree.z_orders.get(&text1_key), Some(&1));
assert_eq!(tree.z_orders.get(&text2_key), Some(&2));
}
#[test]
fn test_anonymous_nested_container() {
let mut builder = DomTreeBuilder::new();
builder.begin_frame();
let a = ElementId::Name("a".into());
let div_key = builder.register(div_node(), true, &[a.clone()]);
let anon_div = builder.register(div_node(), false, &[a.clone()]);
let text_key = builder.register(text_node("x"), false, &[a.clone()]);
builder.exit();
builder.exit();
let tree = builder.finish();
assert_eq!(anon_div, key(&[a.clone()], &[1]));
assert_eq!(text_key, key(&[a.clone()], &[1, 1]));
let div_children = tree.children.get(&div_key).unwrap();
assert_eq!(div_children, &vec![anon_div.clone()]);
let anon_children = tree.children.get(&anon_div).unwrap();
assert_eq!(anon_children, &vec![text_key.clone()]);
}
#[test]
fn test_nested_keyed_child() {
let mut builder = DomTreeBuilder::new();
builder.begin_frame();
let a = ElementId::Name("a".into());
let b = ElementId::Name("b".into());
let div_a = builder.register(div_node(), true, &[a.clone()]);
let div_b = builder.register(div_node(), true, &[a.clone(), b.clone()]);
builder.exit();
builder.exit();
let tree = builder.finish();
assert_eq!(div_a, key(&[a.clone()], &[]));
assert_eq!(div_b, key(&[a.clone(), b.clone()], &[]));
let root_children = tree.children.get(&tree.root).unwrap();
assert_eq!(root_children, &vec![div_a.clone()]);
let a_children = tree.children.get(&div_a).unwrap();
assert_eq!(a_children, &vec![div_b.clone()]);
}
}