use crate::core::{ElementId, ElementState};
use crate::layout::box_model::{ComputedLayout, IntrinsicSize};
use crate::text::TextLayout;
use crate::view::node::{Listener, NodeType, ViewNode};
use crate::view::paint::TextStyle;
use slotmap::SlotMap;
use std::cell::{Cell, RefCell};
use std::sync::Arc;
pub struct ElementEntry {
pub node: ViewNode, pub intrinsic: Cell<IntrinsicSize>,
pub layout: Cell<ComputedLayout>,
pub dirty: Cell<bool>,
pub parent: Option<ElementId>,
pub children: Vec<ElementId>,
pub interact: Cell<ElementState>,
pub scroll_offset: Cell<(f32, f32)>,
pub content_size: Cell<(f32, f32)>,
pub listeners: RefCell<Vec<Listener>>,
pub text_layout_cache: RefCell<Option<Arc<TextLayout>>>,
}
pub struct ElementTree {
entries: SlotMap<ElementId, ElementEntry>,
root: Option<ElementId>,
}
impl ElementTree {
pub fn new() -> Self {
Self {
entries: SlotMap::with_key(),
root: None,
}
}
pub fn create_from_node(&mut self, n: &ViewNode) -> ElementId {
let node = without_children(n);
let listeners = node.listeners().to_vec();
self.entries.insert(ElementEntry {
node,
intrinsic: Cell::new(IntrinsicSize::zero()),
layout: Cell::new(ComputedLayout::default()),
dirty: Cell::new(true),
parent: None,
children: Vec::new(),
interact: Cell::new(ElementState::default()),
scroll_offset: Cell::new((0.0, 0.0)),
content_size: Cell::new((0.0, 0.0)),
listeners: RefCell::new(listeners),
text_layout_cache: RefCell::new(None),
})
}
pub fn create_subtree_from_node(&mut self, n: &ViewNode) -> ElementId {
let id = self.create_from_node(n);
let children: Vec<&ViewNode> = n.children().iter().collect();
for child in children {
let cid = self.create_subtree_from_node(child);
self.add_child(id, cid);
}
id
}
pub fn type_name(&self, id: ElementId) -> Option<&'static str> {
self.entries.get(id).map(|e| e.node.type_name())
}
pub fn get_node(&self, id: ElementId) -> ViewNode {
use crate::layout::style::FlexStyle;
self.entries
.get(id)
.map(|e| e.node.clone())
.unwrap_or(ViewNode::Text {
content: String::new(),
style: TextStyle {
font_size: 0.0,
color: crate::geometry::Color::TRANSPARENT,
..TextStyle::default()
},
layout: FlexStyle::default(),
key: None,
listeners: Vec::new(),
})
}
pub fn get_node_ref(&self, id: ElementId) -> Option<&ViewNode> {
self.entries.get(id).map(|e| &e.node)
}
pub fn listeners(&self, id: ElementId) -> Vec<Listener> {
self.entries
.get(id)
.map(|e| e.listeners.borrow().clone())
.unwrap_or_default()
}
pub fn has_any_listener(&self, id: ElementId) -> bool {
self.entries
.get(id)
.is_some_and(|e| !e.listeners.borrow().is_empty())
}
pub fn is_interactive(&self, id: ElementId) -> bool {
self.entries
.get(id)
.is_some_and(|e| e.node.is_interactive())
}
pub fn has_ime_listener(&self, id: ElementId) -> bool {
self.entries.get(id).is_some_and(|e| {
e.listeners.borrow().iter().any(|l| {
matches!(
l.event,
crate::event::EventType::ImePreedit
| crate::event::EventType::ImeCommit
| crate::event::EventType::ImeDisabled
)
})
})
}
pub fn find_child_by_key(&self, id: ElementId, key: &str) -> Option<ElementId> {
self.entries.get(id).and_then(|e| {
e.children
.iter()
.find(|&&c| self.key_of(c) == Some(key))
.copied()
})
}
pub fn key_of(&self, id: ElementId) -> Option<&str> {
self.entries.get(id).and_then(|e| e.node.key())
}
pub fn state(&self, id: ElementId) -> ElementState {
self.entries
.get(id)
.map(|e| e.interact.get())
.unwrap_or_default()
}
pub fn set_state(&self, id: ElementId, st: ElementState) {
if let Some(e) = self.entries.get(id) {
e.interact.set(st);
}
}
pub fn set_root(&mut self, id: ElementId) {
self.root = Some(id);
}
pub fn root(&self) -> Option<ElementId> {
self.root
}
pub fn parent_of(&self, id: ElementId) -> Option<ElementId> {
self.entries.get(id).and_then(|e| e.parent)
}
pub fn children_of(&self, id: ElementId) -> Vec<ElementId> {
self.entries
.get(id)
.map(|e| e.children.clone())
.unwrap_or_default()
}
pub fn children_ref(&self, id: ElementId) -> &[ElementId] {
self.entries
.get(id)
.map(|e| e.children.as_slice())
.unwrap_or(&[])
}
pub fn contains(&self, id: ElementId) -> bool {
self.entries.contains_key(id)
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn set_intrinsic(&self, id: ElementId, i: IntrinsicSize) {
if let Some(e) = self.entries.get(id) {
e.intrinsic.set(i);
}
}
pub fn intrinsic(&self, id: ElementId) -> IntrinsicSize {
self.entries
.get(id)
.map(|e| e.intrinsic.get())
.unwrap_or(IntrinsicSize::zero())
}
pub fn set_layout(&self, id: ElementId, l: ComputedLayout) {
if let Some(e) = self.entries.get(id) {
e.layout.set(l);
}
}
pub fn layout(&self, id: ElementId) -> ComputedLayout {
self.entries
.get(id)
.map(|e| e.layout.get())
.unwrap_or_default()
}
pub fn scroll_offset(&self, id: ElementId) -> (f32, f32) {
self.entries
.get(id)
.map(|e| e.scroll_offset.get())
.unwrap_or((0.0, 0.0))
}
pub fn set_scroll_offset(&self, id: ElementId, v: (f32, f32)) {
if let Some(e) = self.entries.get(id) {
e.scroll_offset.set(v);
}
}
pub fn content_size(&self, id: ElementId) -> (f32, f32) {
self.entries
.get(id)
.map(|e| e.content_size.get())
.unwrap_or((0.0, 0.0))
}
pub fn set_content_size(&self, id: ElementId, v: (f32, f32)) {
if let Some(e) = self.entries.get(id) {
e.content_size.set(v);
}
}
pub fn scroll_state(&self, id: ElementId) -> Option<crate::state::State<(f32, f32)>> {
self.entries.get(id).and_then(|e| match &e.node {
crate::view::node::ViewNode::Div { layout, .. } => layout.scroll_state.clone(),
_ => None,
})
}
pub fn update_node(&mut self, id: ElementId, new: &ViewNode) {
if let Some(e) = self.entries.get_mut(id) {
let keep_text_cache = matches!(
(&e.node, new),
(
ViewNode::Text {
content: a,
style: sa,
..
},
ViewNode::Text {
content: b,
style: sb,
..
},
) if a == b && sa == sb
);
e.node = without_children(new);
e.dirty.set(true);
if !keep_text_cache {
e.text_layout_cache = RefCell::new(None); }
*e.listeners.borrow_mut() = e.node.listeners().to_vec();
}
}
pub fn set_listeners(&mut self, id: ElementId, listeners: Vec<Listener>) {
if let Some(e) = self.entries.get_mut(id) {
match &mut e.node {
ViewNode::Div { listeners: l, .. }
| ViewNode::Text { listeners: l, .. }
| ViewNode::Image { listeners: l, .. } => *l = listeners.clone(),
}
*e.listeners.borrow_mut() = listeners;
}
}
pub fn add_child(&mut self, pid: ElementId, cid: ElementId) {
if let Some(c) = self.entries.get_mut(cid) {
c.parent = Some(pid);
}
if let Some(p) = self.entries.get_mut(pid) {
p.children.push(cid);
}
}
pub fn insert_child(&mut self, pid: ElementId, pos: usize, cid: ElementId) {
if let Some(c) = self.entries.get_mut(cid) {
c.parent = Some(pid);
}
if let Some(p) = self.entries.get_mut(pid) {
p.children.insert(pos.min(p.children.len()), cid);
}
}
pub fn move_child(&mut self, id: ElementId, new_parent: ElementId, position: usize) {
if let Some(old_parent) = self.parent_of(id)
&& let Some(p) = self.entries.get_mut(old_parent)
{
p.children.retain(|c| *c != id);
}
if let Some(c) = self.entries.get_mut(id) {
c.parent = Some(new_parent);
c.dirty.set(true);
}
if let Some(p) = self.entries.get_mut(new_parent) {
p.children.insert(position.min(p.children.len()), id);
}
}
pub fn remove(&mut self, id: ElementId) -> bool {
if !self.entries.contains_key(id) {
return false;
}
let sub = self.collect_subtree(id);
if let Some(e) = self.entries.get(id)
&& let Some(pid) = e.parent
&& let Some(p) = self.entries.get_mut(pid)
{
p.children.retain(|c| *c != id);
}
if self.root == Some(id) {
self.root = None;
}
for rid in sub.iter().rev().copied() {
self.entries.remove(rid);
}
true
}
fn collect_subtree(&self, rid: ElementId) -> Vec<ElementId> {
let mut ids = vec![rid];
if let Some(e) = self.entries.get(rid) {
for c in &e.children {
ids.extend(self.collect_subtree(*c));
}
}
ids
}
pub fn path_to(&self, tgt: ElementId) -> Vec<ElementId> {
let mut p = Vec::new();
let mut c = tgt;
while let Some(e) = self.entries.get(c) {
p.push(c);
c = match e.parent {
Some(x) => x,
None => break,
};
}
p.reverse();
p
}
pub fn config_eq(&self, id: ElementId, other: &ViewNode) -> bool {
self.entries
.get(id)
.map(|e| e.node.config_eq(other))
.unwrap_or(false)
}
pub fn node_type_of(&self, id: ElementId) -> Option<NodeType> {
self.entries.get(id).map(|e| e.node.node_type())
}
pub fn is_dirty(&self, id: ElementId) -> bool {
self.entries.get(id).map(|e| e.dirty.get()).unwrap_or(false)
}
pub fn subtree_has_dirty(&self, id: ElementId) -> bool {
if self.is_dirty(id) {
return true;
}
self.children_ref(id)
.iter()
.any(|c| self.subtree_has_dirty(*c))
}
pub fn translate_subtree(&self, id: ElementId, dx: f32, dy: f32) {
if dx == 0.0 && dy == 0.0 {
return;
}
self.translate_subtree_rec(id, dx, dy);
}
fn translate_subtree_rec(&self, id: ElementId, dx: f32, dy: f32) {
if let Some(e) = self.entries.get(id) {
let mut l = e.layout.get();
l.x += dx;
l.y += dy;
e.layout.set(l);
let children: Vec<ElementId> = e.children.clone();
for c in children {
self.translate_subtree_rec(c, dx, dy);
}
}
}
pub fn has_dirty_node(&self) -> bool {
self.entries.iter().any(|(_, e)| e.dirty.get())
}
pub fn mark_dirty_all(&mut self) {
for (_, e) in self.entries.iter_mut() {
e.dirty.set(true);
}
}
pub fn clear_dirty(&mut self) {
for (_, e) in self.entries.iter_mut() {
e.dirty.set(false);
}
}
pub fn peek_text_layout_cache(&self, id: ElementId) -> Option<Arc<TextLayout>> {
self.entries
.get(id)
.and_then(|e| e.text_layout_cache.borrow().clone())
}
pub fn set_text_layout_cache(&self, id: ElementId, layout: Arc<TextLayout>) {
if let Some(e) = self.entries.get(id) {
*e.text_layout_cache.borrow_mut() = Some(layout);
}
}
}
impl Default for ElementTree {
fn default() -> Self {
Self::new()
}
}
fn without_children(n: &ViewNode) -> ViewNode {
match n {
ViewNode::Div {
layout,
paint,
key,
listeners,
..
} => ViewNode::Div {
layout: layout.clone(),
paint: paint.clone(),
key: key.clone(),
children: vec![],
listeners: listeners.clone(),
},
ViewNode::Text {
content,
style,
layout,
key,
listeners,
} => ViewNode::Text {
content: content.clone(),
style: style.clone(),
layout: layout.clone(),
key: key.clone(),
listeners: listeners.clone(),
},
ViewNode::Image {
data,
style,
layout,
key,
listeners,
} => ViewNode::Image {
data: std::sync::Arc::clone(data),
style: *style,
layout: layout.clone(),
key: key.clone(),
listeners: listeners.clone(),
},
}
}