use crate::render_tree::{RenderNode, RenderNodeType};
use crate::vnode::VNode;
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Debug, Clone)]
pub enum Patch {
Replace {
old: Rc<RefCell<RenderNode>>,
new: VNode,
},
UpdateText {
node: Rc<RefCell<RenderNode>>,
new_text: String,
new_style: Option<crate::style::TextStyle>,
},
UpdateRichText {
node: Rc<RefCell<RenderNode>>,
new_spans: Vec<crate::node::TextSpan>,
},
UpdateProps {
node: Rc<RefCell<RenderNode>>,
div: crate::node::Div<VNode>,
},
AddChild {
parent: Rc<RefCell<RenderNode>>,
child: VNode,
index: usize,
},
RemoveChild {
parent: Rc<RefCell<RenderNode>>,
index: usize,
},
ReorderChildren {
parent: Rc<RefCell<RenderNode>>,
moves: Vec<Move>,
},
}
#[derive(Debug, Clone)]
pub struct Move {
pub from: usize,
pub to: usize,
}
pub struct DiffContext {
pub patches: Vec<Patch>,
}
pub fn diff(old: &Rc<RefCell<RenderNode>>, new: &VNode) -> Vec<Patch> {
let mut context = DiffContext {
patches: Vec::new(),
};
diff_node(&mut context, old, new);
context.patches
}
fn diff_node(context: &mut DiffContext, old: &Rc<RefCell<RenderNode>>, new: &VNode) {
let old_ref = old.borrow();
match (&old_ref.node_type, new) {
(RenderNodeType::Text(old_text), VNode::Text(new_text)) => {
let old_style = old_ref.text_style.as_ref();
let new_style = new_text.style.as_ref();
let style_changed = match (old_style, new_style) {
(None, None) => false,
(Some(old), Some(new)) => old != new,
_ => true,
};
if old_text != &new_text.content || style_changed {
context.patches.push(Patch::UpdateText {
node: old.clone(),
new_text: new_text.content.clone(),
new_style: new_text.style.clone(),
});
}
}
(RenderNodeType::RichText(old_spans), VNode::RichText(new_rich)) => {
if old_spans != &new_rich.spans {
context.patches.push(Patch::UpdateRichText {
node: old.clone(),
new_spans: new_rich.spans.clone(),
});
}
}
(RenderNodeType::Element, VNode::Div(new_div)) => {
diff_div(context, old, &old_ref, new_div);
}
_ => {
context.patches.push(Patch::Replace {
old: old.clone(),
new: new.clone(),
});
}
}
}
fn diff_div(
context: &mut DiffContext,
old_node: &Rc<RefCell<RenderNode>>,
old_ref: &RenderNode,
new_div: &crate::node::Div<VNode>,
) {
let props_changed = {
let old_style = &old_ref.style;
let is_focused = old_ref.focused;
let new_style = if is_focused {
crate::style::Style::merge(new_div.styles.base.clone(), new_div.styles.focus.clone())
} else {
new_div.styles.base.clone()
};
let new_style = &new_style;
let dimensions_changed = match (old_style, new_style) {
(Some(old_s), Some(new_s)) => {
old_s.width != new_s.width || old_s.height != new_s.height
}
(None, Some(_)) | (Some(_), None) => true,
(None, None) => false,
};
old_style != new_style || dimensions_changed
};
if props_changed {
context.patches.push(Patch::UpdateProps {
node: old_node.clone(),
div: new_div.clone(),
});
}
diff_children(context, old_node, &old_ref.children, &new_div.children);
}
fn diff_children(
context: &mut DiffContext,
parent: &Rc<RefCell<RenderNode>>,
old_children: &[Rc<RefCell<RenderNode>>],
new_children: &[VNode],
) {
let old_len = old_children.len();
let new_len = new_children.len();
let min_len = old_len.min(new_len);
for i in 0..min_len {
diff_node(context, &old_children[i], &new_children[i]);
}
if new_len > old_len {
for (i, child) in new_children.iter().enumerate().skip(old_len) {
context.patches.push(Patch::AddChild {
parent: parent.clone(),
child: child.clone(),
index: i,
});
}
} else if old_len > new_len {
for i in (new_len..old_len).rev() {
context.patches.push(Patch::RemoveChild {
parent: parent.clone(),
index: i,
});
}
}
}