use crate::diff::{Patch, diff};
use crate::render_tree::{RenderNode, RenderNodeType, RenderTree};
use crate::utils::display_width;
use crate::vnode::VNode;
use std::cell::RefCell;
use std::rc::Rc;
pub struct VDom {
render_tree: RenderTree,
current_vnode: Option<VNode>,
}
impl VDom {
pub fn new() -> Self {
Self {
render_tree: RenderTree::new(),
current_vnode: None,
}
}
pub fn render(&mut self, vnode: VNode) {
match &self.render_tree.root {
Some(root) => {
let patches = diff(root, &vnode);
self.apply_patches(patches);
}
None => {
let render_node = self.create_render_node(&vnode);
self.render_tree.set_root(render_node);
}
}
self.current_vnode = Some(vnode);
}
pub fn layout(&mut self, width: u16, height: u16) {
self.render_tree.layout(width, height);
}
pub fn get_render_tree(&self) -> &RenderTree {
&self.render_tree
}
fn create_render_node(&self, vnode: &VNode) -> Rc<RefCell<RenderNode>> {
match vnode {
VNode::Div(container) => self.create_div_node(container),
VNode::Text(text) => self.create_text_node(text),
VNode::RichText(rich) => self.create_rich_text_node(rich),
}
}
fn create_div_node(&self, div: &crate::node::Div<VNode>) -> Rc<RefCell<RenderNode>> {
let mut render_node = RenderNode::element();
let effective_style = if div.focused {
let default_focus = if div.focusable {
Some(crate::style::Style::default_focus())
} else {
None
};
let focus_with_defaults =
crate::style::Style::merge(default_focus, div.styles.focus.clone());
crate::style::Style::merge(div.styles.base.clone(), focus_with_defaults)
} else {
div.styles.base.clone()
};
if let Some(style) = effective_style {
match style.width {
Some(crate::style::Dimension::Fixed(width)) => {
render_node.width = width;
}
Some(crate::style::Dimension::Percentage(_)) => {
}
Some(crate::style::Dimension::Auto) => {
}
Some(crate::style::Dimension::Content) => {
}
None => {}
}
match style.height {
Some(crate::style::Dimension::Fixed(height)) => {
render_node.height = height;
}
Some(crate::style::Dimension::Percentage(_)) => {
}
Some(crate::style::Dimension::Auto) => {
}
Some(crate::style::Dimension::Content) => {
}
None => {}
}
render_node.position_type = style.position.unwrap_or(crate::style::Position::Relative);
render_node.z_index = style.z_index.unwrap_or(0);
render_node.style = Some(style);
}
render_node.styles = div.styles.clone();
render_node.events = div.events.clone();
render_node.focusable = div.focusable;
render_node.focused = div.focused;
let node_rc = Rc::new(RefCell::new(render_node));
for child_vnode in &div.children {
let child_render = match child_vnode {
VNode::Text(text) => {
let mut text_node = RenderNode::text(&text.content);
text_node.width = display_width(&text.content) as u16;
text_node.height = 1;
if let Some(ts) = &text.style {
text_node.text_color = ts.color;
text_node.text_style = Some(ts.clone());
text_node.style = ts.background.map(|bg| crate::style::Style {
background: Some(bg),
..Default::default()
});
}
Rc::new(RefCell::new(text_node))
}
VNode::RichText(rich) => {
let mut rich_node =
RenderNode::new(RenderNodeType::RichText(rich.spans.clone()));
rich_node.width = rich
.spans
.iter()
.map(|span| display_width(&span.content) as u16)
.sum();
rich_node.height = 1;
if let Some(ts) = &rich.style {
rich_node.text_style = Some(ts.clone());
if !rich.spans.is_empty() {
let first_color = rich.spans[0].style.as_ref().and_then(|s| s.color);
if rich.spans.iter().all(|span| {
span.style.as_ref().and_then(|s| s.color) == first_color
}) {
rich_node.text_color = first_color;
}
}
}
Rc::new(RefCell::new(rich_node))
}
VNode::Div(_) => self.create_render_node(child_vnode),
};
RenderNode::add_child_with_parent(&node_rc, child_render);
}
node_rc
}
fn create_text_node(&self, text: &crate::node::Text) -> Rc<RefCell<RenderNode>> {
let mut render_node = RenderNode::text(&text.content);
render_node.width = display_width(&text.content) as u16;
render_node.height = 1;
if let Some(ts) = &text.style {
render_node.text_color = ts.color;
render_node.text_style = Some(ts.clone());
render_node.style = ts.background.map(|bg| crate::style::Style {
background: Some(bg),
..Default::default()
});
}
Rc::new(RefCell::new(render_node))
}
fn create_rich_text_node(&self, rich: &crate::node::RichText) -> Rc<RefCell<RenderNode>> {
let mut render_node = RenderNode::new(RenderNodeType::RichText(rich.spans.clone()));
render_node.width = rich
.spans
.iter()
.map(|span| display_width(&span.content) as u16)
.sum();
render_node.height = 1;
if let Some(ts) = &rich.style {
render_node.text_style = Some(ts.clone());
if !rich.spans.is_empty() {
let first_color = rich.spans[0].style.as_ref().and_then(|s| s.color);
if rich
.spans
.iter()
.all(|span| span.style.as_ref().and_then(|s| s.color) == first_color)
{
render_node.text_color = first_color;
}
}
}
Rc::new(RefCell::new(render_node))
}
fn apply_patches(&mut self, patches: Vec<Patch>) {
for patch in patches {
self.apply_patch(patch);
}
}
fn apply_patch(&mut self, patch: Patch) {
match patch {
Patch::Replace { old, new } => {
let new_render = self.create_render_node(&new);
new_render.borrow_mut().mark_dirty();
if let Some(parent) = &old.borrow().parent {
if let Some(parent_strong) = parent.upgrade() {
let mut parent_ref = parent_strong.borrow_mut();
if let Some(index) =
parent_ref.children.iter().position(|c| Rc::ptr_eq(c, &old))
{
parent_ref.children[index] = new_render.clone();
new_render.borrow_mut().parent = Some(Rc::downgrade(&parent_strong));
}
parent_ref.mark_dirty();
}
} else {
self.render_tree.set_root(new_render);
}
}
Patch::UpdateText {
node,
new_text,
new_style,
} => {
let mut node_ref = node.borrow_mut();
node_ref.width = display_width(&new_text) as u16;
node_ref.height = 1;
node_ref.node_type = RenderNodeType::Text(new_text);
node_ref.text_style = new_style.clone();
if let Some(ts) = &new_style {
node_ref.text_color = ts.color;
node_ref.style = ts.background.map(|bg| crate::style::Style {
background: Some(bg),
..Default::default()
});
} else {
node_ref.text_color = None;
if let Some(existing_style) = &mut node_ref.style {
existing_style.background = None;
}
}
node_ref.mark_dirty();
}
Patch::UpdateRichText { node, new_spans } => {
let mut node_ref = node.borrow_mut();
node_ref.width = new_spans
.iter()
.map(|span| display_width(&span.content) as u16)
.sum();
node_ref.height = 1;
node_ref.node_type = RenderNodeType::RichText(new_spans);
node_ref.mark_dirty();
}
Patch::UpdateProps { node, div } => {
let mut node_ref = node.borrow_mut();
let is_focused = node_ref.focused;
let effective_style = if is_focused {
let default_focus = if div.focusable {
Some(crate::style::Style::default_focus())
} else {
None
};
let focus_with_defaults =
crate::style::Style::merge(default_focus, div.styles.focus.clone());
crate::style::Style::merge(div.styles.base.clone(), focus_with_defaults)
} else {
div.styles.base.clone()
};
if let Some(style) = effective_style {
match style.width {
Some(crate::style::Dimension::Fixed(width)) => {
node_ref.width = width;
}
Some(crate::style::Dimension::Percentage(_)) => {
}
Some(crate::style::Dimension::Auto) => {
}
Some(crate::style::Dimension::Content) => {
}
None => {}
}
match style.height {
Some(crate::style::Dimension::Fixed(height)) => {
node_ref.height = height;
}
Some(crate::style::Dimension::Percentage(_)) => {
}
Some(crate::style::Dimension::Auto) => {
}
Some(crate::style::Dimension::Content) => {
}
None => {}
}
node_ref.style = Some(style);
}
node_ref.styles = div.styles.clone();
node_ref.events = div.events.clone();
node_ref.focusable = div.focusable;
node_ref.focused = is_focused;
node_ref.mark_dirty();
}
Patch::AddChild {
parent,
child,
index,
} => {
let child_render = self.create_render_node(&child);
{
let mut parent_ref = parent.borrow_mut();
if index >= parent_ref.children.len() {
parent_ref.children.push(child_render.clone());
} else {
parent_ref.children.insert(index, child_render.clone());
}
parent_ref.mark_dirty();
}
child_render.borrow_mut().parent = Some(Rc::downgrade(&parent));
}
Patch::RemoveChild { parent, index } => {
let mut parent_ref = parent.borrow_mut();
if index < parent_ref.children.len() {
parent_ref.children.remove(index);
parent_ref.mark_dirty();
}
}
Patch::ReorderChildren { parent, moves } => {
let mut parent_ref = parent.borrow_mut();
for mov in moves {
if mov.from < parent_ref.children.len() && mov.to < parent_ref.children.len() {
let child = parent_ref.children.remove(mov.from);
parent_ref.children.insert(mov.to, child);
}
}
parent_ref.mark_dirty();
}
}
}
}
impl Default for VDom {
fn default() -> Self {
Self::new()
}
}