use alloc::{collections::BTreeMap, collections::VecDeque, string::String, vec::Vec};
use core::hash::Hash;
use azul_css::props::property::{CssPropertyType, RelayoutScope};
use crate::{
dom::{DomId, DomNodeHash, DomNodeId, NodeData, NodeType, IdOrClass},
events::{
ComponentEventFilter, EventData, EventFilter, EventPhase, EventSource, EventType,
LifecycleEventData, LifecycleReason, SyntheticEvent,
},
geom::LogicalRect,
id::NodeId,
styled_dom::{ChangedCssProperty, NodeHierarchyItemId, NodeHierarchyItem, RestyleResult, StyledNodeState},
task::Instant,
OrderedMap,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct NodeChangeSet {
pub bits: u32,
}
impl NodeChangeSet {
pub const NODE_TYPE_CHANGED: u32 = 0b0000_0000_0000_0001;
pub const TEXT_CONTENT: u32 = 0b0000_0000_0000_0010;
pub const IDS_AND_CLASSES: u32 = 0b0000_0000_0000_0100;
pub const INLINE_STYLE_LAYOUT: u32 = 0b0000_0000_0000_1000;
pub const CHILDREN_CHANGED: u32 = 0b0000_0000_0001_0000;
pub const IMAGE_CHANGED: u32 = 0b0000_0000_0010_0000;
pub const CONTENTEDITABLE: u32 = 0b0000_0000_0100_0000;
pub const TAB_INDEX: u32 = 0b0000_0000_1000_0000;
pub const INLINE_STYLE_PAINT: u32 = 0b0000_0001_0000_0000;
pub const STYLED_STATE: u32 = 0b0000_0010_0000_0000;
pub const CALLBACKS: u32 = 0b0000_0100_0000_0000;
pub const DATASET: u32 = 0b0000_1000_0000_0000;
pub const ACCESSIBILITY: u32 = 0b0001_0000_0000_0000;
pub const AFFECTS_LAYOUT: u32 = Self::NODE_TYPE_CHANGED
| Self::TEXT_CONTENT
| Self::IDS_AND_CLASSES
| Self::INLINE_STYLE_LAYOUT
| Self::CHILDREN_CHANGED
| Self::IMAGE_CHANGED
| Self::CONTENTEDITABLE;
pub const AFFECTS_PAINT: u32 = Self::INLINE_STYLE_PAINT
| Self::STYLED_STATE;
pub const fn empty() -> Self {
Self { bits: 0 }
}
pub const fn is_empty(&self) -> bool {
self.bits == 0
}
pub const fn contains(&self, flag: u32) -> bool {
(self.bits & flag) == flag
}
pub const fn intersects(&self, mask: u32) -> bool {
(self.bits & mask) != 0
}
pub fn insert(&mut self, flag: u32) {
self.bits |= flag;
}
pub const fn is_visually_unchanged(&self) -> bool {
!self.intersects(Self::AFFECTS_LAYOUT) && !self.intersects(Self::AFFECTS_PAINT)
}
pub const fn needs_layout(&self) -> bool {
self.intersects(Self::AFFECTS_LAYOUT)
}
pub const fn needs_paint(&self) -> bool {
self.intersects(Self::AFFECTS_PAINT)
}
}
impl core::ops::BitOrAssign for NodeChangeSet {
fn bitor_assign(&mut self, rhs: Self) {
self.bits |= rhs.bits;
}
}
impl core::ops::BitOr for NodeChangeSet {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
Self { bits: self.bits | rhs.bits }
}
}
#[derive(Debug, Clone)]
pub struct ExtendedDiffResult {
pub diff: DiffResult,
pub node_changes: Vec<(NodeId, NodeId, NodeChangeSet)>,
}
impl Default for ExtendedDiffResult {
fn default() -> Self {
Self {
diff: DiffResult::default(),
node_changes: Vec::new(),
}
}
}
pub fn compute_node_changes(
old_node: &NodeData,
new_node: &NodeData,
old_styled_state: Option<&StyledNodeState>,
new_styled_state: Option<&StyledNodeState>,
) -> NodeChangeSet {
let mut changes = NodeChangeSet::empty();
if core::mem::discriminant(old_node.get_node_type())
!= core::mem::discriminant(new_node.get_node_type())
{
changes.insert(NodeChangeSet::NODE_TYPE_CHANGED);
return changes; }
match (old_node.get_node_type(), new_node.get_node_type()) {
(NodeType::Text(old_text), NodeType::Text(new_text)) => {
if old_text.as_str() != new_text.as_str() {
changes.insert(NodeChangeSet::TEXT_CONTENT);
}
}
(NodeType::Image(old_img), NodeType::Image(new_img)) => {
use std::hash::Hasher;
let hash_img = |img: &crate::resources::ImageRef| -> u64 {
let mut h = std::hash::DefaultHasher::new();
img.hash(&mut h);
h.finish()
};
if hash_img(old_img) != hash_img(new_img) {
changes.insert(NodeChangeSet::IMAGE_CHANGED);
}
}
_ => {} }
{
use crate::dom::AttributeType;
let old_ids_classes: Vec<_> = old_node.attributes().as_ref().iter()
.filter(|a| matches!(a, AttributeType::Id(_) | AttributeType::Class(_)))
.collect();
let new_ids_classes: Vec<_> = new_node.attributes().as_ref().iter()
.filter(|a| matches!(a, AttributeType::Id(_) | AttributeType::Class(_)))
.collect();
if old_ids_classes != new_ids_classes {
changes.insert(NodeChangeSet::IDS_AND_CLASSES);
}
}
if old_node.style != new_node.style {
let mut has_layout = false;
let mut has_paint = false;
let mut old_map = OrderedMap::default();
for (prop, conds) in old_node.style.iter_inline_properties() {
old_map.insert(prop.get_type(), (prop, conds));
}
let mut seen_types = OrderedMap::default();
for (prop, conds) in new_node.style.iter_inline_properties() {
let prop_type = prop.get_type();
seen_types.insert(prop_type, ());
match old_map.get(&prop_type) {
Some(&(old_prop, old_conds))
if old_prop == prop
&& old_conds.as_slice() == conds.as_slice() => {} _ => {
let scope = prop_type.relayout_scope(true);
if scope != RelayoutScope::None {
has_layout = true;
} else {
has_paint = true;
}
}
}
}
for (prop_type, _) in old_map.iter() {
if !seen_types.contains_key(prop_type) {
let scope = prop_type.relayout_scope(true);
if scope != RelayoutScope::None {
has_layout = true;
} else {
has_paint = true;
}
}
}
if has_layout {
changes.insert(NodeChangeSet::INLINE_STYLE_LAYOUT);
}
if has_paint {
changes.insert(NodeChangeSet::INLINE_STYLE_PAINT);
}
}
{
let old_cbs = old_node.callbacks.as_ref();
let new_cbs = new_node.callbacks.as_ref();
if old_cbs.len() != new_cbs.len() {
changes.insert(NodeChangeSet::CALLBACKS);
} else {
for (o, n) in old_cbs.iter().zip(new_cbs.iter()) {
if o.event != n.event || o.callback != n.callback {
changes.insert(NodeChangeSet::CALLBACKS);
break;
}
}
}
}
if old_node.get_dataset() != new_node.get_dataset() {
changes.insert(NodeChangeSet::DATASET);
}
if old_node.is_contenteditable() != new_node.is_contenteditable() {
changes.insert(NodeChangeSet::CONTENTEDITABLE);
}
if old_node.get_tab_index() != new_node.get_tab_index() {
changes.insert(NodeChangeSet::TAB_INDEX);
}
if old_styled_state != new_styled_state {
changes.insert(NodeChangeSet::STYLED_STATE);
}
changes
}
pub fn calculate_reconciliation_key(
node_data: &[NodeData],
hierarchy: &[NodeHierarchyItem],
node_id: NodeId,
) -> u64 {
use core::hash::Hasher;
let node = &node_data[node_id.index()];
if let Some(key) = node.get_key() {
return key;
}
for attr in node.attributes().as_ref().iter() {
if let Some(id) = attr.as_id() {
let mut hasher = std::hash::DefaultHasher::new();
id.hash(&mut hasher);
return hasher.finish();
}
}
let mut hasher = std::hash::DefaultHasher::new();
core::mem::discriminant(node.get_node_type()).hash(&mut hasher);
for attr in node.attributes().as_ref().iter() {
if let Some(class) = attr.as_class() {
class.hash(&mut hasher);
}
}
if let Some(hierarchy_item) = hierarchy.get(node_id.index()) {
if let Some(parent_id) = hierarchy_item.parent_id() {
let mut sibling_index: usize = 0;
let parent_hierarchy = &hierarchy[parent_id.index()];
let mut current = parent_hierarchy.first_child_id(parent_id);
while let Some(sibling_id) = current {
if sibling_id == node_id {
break;
}
let sibling = &node_data[sibling_id.index()];
if core::mem::discriminant(sibling.get_node_type())
== core::mem::discriminant(node.get_node_type())
{
sibling_index += 1;
}
current = hierarchy[sibling_id.index()].next_sibling_id();
}
sibling_index.hash(&mut hasher);
let parent_key = calculate_reconciliation_key(node_data, hierarchy, parent_id);
parent_key.hash(&mut hasher);
}
}
hasher.finish()
}
pub fn precompute_reconciliation_keys(
node_data: &[NodeData],
hierarchy: &[NodeHierarchyItem],
) -> Vec<u64> {
(0..node_data.len())
.map(|idx| calculate_reconciliation_key(node_data, hierarchy, NodeId::new(idx)))
.collect()
}
#[derive(Debug, Clone, Copy)]
pub struct NodeMove {
pub old_node_id: NodeId,
pub new_node_id: NodeId,
}
#[derive(Debug, Clone)]
pub struct DiffResult {
pub events: Vec<SyntheticEvent>,
pub node_moves: Vec<NodeMove>,
}
impl Default for DiffResult {
fn default() -> Self {
Self {
events: Vec::new(),
node_moves: Vec::new(),
}
}
}
pub fn reconcile_dom(
old_node_data: &[NodeData],
new_node_data: &[NodeData],
old_hierarchy: &[NodeHierarchyItem],
new_hierarchy: &[NodeHierarchyItem],
old_layout: &OrderedMap<NodeId, LogicalRect>,
new_layout: &OrderedMap<NodeId, LogicalRect>,
dom_id: DomId,
timestamp: Instant,
) -> DiffResult {
let mut result = DiffResult::default();
let old_rec_keys = precompute_reconciliation_keys(old_node_data, old_hierarchy);
let mut old_by_rec_key: OrderedMap<u64, VecDeque<NodeId>> = OrderedMap::default();
let mut old_hashed: OrderedMap<DomNodeHash, VecDeque<NodeId>> = OrderedMap::default();
let mut old_structural: OrderedMap<DomNodeHash, VecDeque<NodeId>> = OrderedMap::default();
let mut old_nodes_consumed = vec![false; old_node_data.len()];
for (idx, node) in old_node_data.iter().enumerate() {
let id = NodeId::new(idx);
old_by_rec_key.entry(old_rec_keys[idx]).or_default().push_back(id);
let hash = node.calculate_node_data_hash();
old_hashed.entry(hash).or_default().push_back(id);
let structural_hash = node.calculate_structural_hash();
old_structural.entry(structural_hash).or_default().push_back(id);
}
fn pop_first_unconsumed(
queue: &mut VecDeque<NodeId>,
consumed: &[bool],
) -> Option<NodeId> {
while let Some(&old_id) = queue.front() {
if consumed[old_id.index()] {
queue.pop_front();
} else {
queue.pop_front();
return Some(old_id);
}
}
None
}
for (new_idx, new_node) in new_node_data.iter().enumerate() {
let new_id = NodeId::new(new_idx);
let mut matched_old_id = None;
let mut matched_by_rec_key = false;
let has_explicit_key = new_node.get_key().is_some();
let new_rec_key =
calculate_reconciliation_key(new_node_data, new_hierarchy, new_id);
if let Some(queue) = old_by_rec_key.get_mut(&new_rec_key) {
if let Some(old_id) = pop_first_unconsumed(queue, &old_nodes_consumed) {
matched_old_id = Some(old_id);
matched_by_rec_key = true;
}
}
if !has_explicit_key && matched_old_id.is_none() {
let hash = new_node.calculate_node_data_hash();
if let Some(queue) = old_hashed.get_mut(&hash) {
if let Some(old_id) = pop_first_unconsumed(queue, &old_nodes_consumed) {
matched_old_id = Some(old_id);
}
}
if matched_old_id.is_none() {
let structural_hash = new_node.calculate_structural_hash();
if let Some(queue) = old_structural.get_mut(&structural_hash) {
if let Some(old_id) = pop_first_unconsumed(queue, &old_nodes_consumed) {
matched_old_id = Some(old_id);
}
}
}
}
if let Some(old_id) = matched_old_id {
old_nodes_consumed[old_id.index()] = true;
result.node_moves.push(NodeMove {
old_node_id: old_id,
new_node_id: new_id,
});
let old_rect = old_layout.get(&old_id).copied().unwrap_or(LogicalRect::zero());
let new_rect = new_layout.get(&new_id).copied().unwrap_or(LogicalRect::zero());
if old_rect.size != new_rect.size {
if has_resize_callback(new_node) {
result.events.push(create_lifecycle_event(
EventType::Resize,
new_id,
dom_id,
×tamp,
LifecycleEventData {
reason: LifecycleReason::Resize,
previous_bounds: Some(old_rect),
current_bounds: new_rect,
},
));
}
}
if matched_by_rec_key {
let old_hash = old_node_data[old_id.index()].calculate_node_data_hash();
let new_hash = new_node.calculate_node_data_hash();
if old_hash != new_hash && has_update_callback(new_node) {
result.events.push(create_lifecycle_event(
EventType::Update,
new_id,
dom_id,
×tamp,
LifecycleEventData {
reason: LifecycleReason::Update,
previous_bounds: Some(old_rect),
current_bounds: new_rect,
},
));
}
}
} else {
if has_mount_callback(new_node) {
let bounds = new_layout.get(&new_id).copied().unwrap_or(LogicalRect::zero());
result.events.push(create_lifecycle_event(
EventType::Mount,
new_id,
dom_id,
×tamp,
LifecycleEventData {
reason: LifecycleReason::InitialMount,
previous_bounds: None,
current_bounds: bounds,
},
));
}
}
}
for (old_idx, consumed) in old_nodes_consumed.iter().enumerate() {
if !consumed {
let old_id = NodeId::new(old_idx);
let old_node = &old_node_data[old_idx];
if has_unmount_callback(old_node) {
let bounds = old_layout.get(&old_id).copied().unwrap_or(LogicalRect::zero());
result.events.push(create_lifecycle_event(
EventType::Unmount,
old_id,
dom_id,
×tamp,
LifecycleEventData {
reason: LifecycleReason::Unmount,
previous_bounds: Some(bounds),
current_bounds: LogicalRect::zero(),
},
));
}
}
}
result
}
fn create_lifecycle_event(
event_type: EventType,
node_id: NodeId,
dom_id: DomId,
timestamp: &Instant,
data: LifecycleEventData,
) -> SyntheticEvent {
let dom_node_id = DomNodeId {
dom: dom_id,
node: NodeHierarchyItemId::from_crate_internal(Some(node_id)),
};
SyntheticEvent {
event_type,
source: EventSource::Lifecycle,
phase: EventPhase::Target,
target: dom_node_id,
current_target: dom_node_id,
timestamp: timestamp.clone(),
data: EventData::Lifecycle(data),
stopped: false,
stopped_immediate: false,
prevented_default: false,
}
}
fn has_mount_callback(node: &NodeData) -> bool {
node.get_callbacks().iter().any(|cb| {
matches!(
cb.event,
EventFilter::Component(ComponentEventFilter::AfterMount)
)
})
}
fn has_unmount_callback(node: &NodeData) -> bool {
node.get_callbacks().iter().any(|cb| {
matches!(
cb.event,
EventFilter::Component(ComponentEventFilter::BeforeUnmount)
)
})
}
fn has_resize_callback(node: &NodeData) -> bool {
node.get_callbacks().iter().any(|cb| {
matches!(
cb.event,
EventFilter::Component(ComponentEventFilter::NodeResized)
)
})
}
fn has_update_callback(node: &NodeData) -> bool {
node.get_callbacks().iter().any(|cb| {
matches!(
cb.event,
EventFilter::Component(ComponentEventFilter::Updated)
)
})
}
pub fn create_migration_map(node_moves: &[NodeMove]) -> OrderedMap<NodeId, NodeId> {
let mut map = OrderedMap::default();
for m in node_moves {
map.insert(m.old_node_id, m.new_node_id);
}
map
}
pub fn transfer_states(
old_node_data: &mut [NodeData],
new_node_data: &mut [NodeData],
node_moves: &[NodeMove],
) {
use crate::refany::OptionRefAny;
for movement in node_moves {
let old_idx = movement.old_node_id.index();
let new_idx = movement.new_node_id.index();
if old_idx >= old_node_data.len() || new_idx >= new_node_data.len() {
continue;
}
let merge_callback = match new_node_data[new_idx].get_merge_callback() {
Some(cb) => cb,
None => continue, };
let old_dataset = old_node_data[old_idx].take_dataset();
let new_dataset = new_node_data[new_idx].take_dataset();
match (new_dataset, old_dataset) {
(Some(new_data), Some(old_data)) => {
let merged = (merge_callback.cb)(new_data, old_data);
new_node_data[new_idx].set_dataset(OptionRefAny::Some(merged));
}
(new_ds, old_ds) => {
if let Some(ds) = new_ds {
new_node_data[new_idx].set_dataset(OptionRefAny::Some(ds));
}
if let Some(ds) = old_ds {
old_node_data[old_idx].set_dataset(OptionRefAny::Some(ds));
}
}
}
}
}
pub fn calculate_contenteditable_key(
node_data: &[NodeData],
hierarchy: &[crate::styled_dom::NodeHierarchyItem],
node_id: NodeId,
) -> u64 {
use std::hash::Hasher;
let node = &node_data[node_id.index()];
if let Some(explicit_key) = node.get_key() {
return explicit_key;
}
for attr in node.attributes().as_ref().iter() {
if let Some(id) = attr.as_id() {
let mut hasher = std::hash::DefaultHasher::new(); hasher.write(id.as_bytes());
return hasher.finish();
}
}
let mut hasher = std::hash::DefaultHasher::new();
let parent_key = if let Some(parent_id) = hierarchy.get(node_id.index()).and_then(|h| h.parent_id()) {
calculate_contenteditable_key(node_data, hierarchy, parent_id)
} else {
0u64 };
hasher.write(&parent_key.to_le_bytes());
let node_discriminant = core::mem::discriminant(node.get_node_type());
let nth_of_type = if let Some(parent_id) = hierarchy.get(node_id.index()).and_then(|h| h.parent_id()) {
let mut count = 0u32;
let mut sibling_id = hierarchy.get(parent_id.index()).and_then(|h| h.first_child_id(parent_id));
while let Some(sib_id) = sibling_id {
if sib_id == node_id {
break;
}
let sibling_discriminant = core::mem::discriminant(node_data[sib_id.index()].get_node_type());
if sibling_discriminant == node_discriminant {
count += 1;
}
sibling_id = hierarchy.get(sib_id.index()).and_then(|h| h.next_sibling_id());
}
count
} else {
0
};
hasher.write(&nth_of_type.to_le_bytes());
node_discriminant.hash(&mut hasher);
for attr in node.attributes().as_ref().iter() {
if let Some(class) = attr.as_class() {
hasher.write(class.as_bytes());
}
}
hasher.finish()
}
pub fn reconcile_cursor_position(
old_text: &str,
new_text: &str,
old_cursor_byte: usize,
) -> usize {
if old_text == new_text {
return old_cursor_byte;
}
if old_text.is_empty() {
return new_text.len();
}
if new_text.is_empty() {
return 0;
}
let common_prefix_bytes = old_text
.bytes()
.zip(new_text.bytes())
.take_while(|(a, b)| a == b)
.count();
if old_cursor_byte <= common_prefix_bytes {
return old_cursor_byte.min(new_text.len());
}
let common_suffix_bytes = old_text
.bytes()
.rev()
.zip(new_text.bytes().rev())
.take_while(|(a, b)| a == b)
.count();
let old_suffix_start = old_text.len().saturating_sub(common_suffix_bytes);
let new_suffix_start = new_text.len().saturating_sub(common_suffix_bytes);
if old_cursor_byte >= old_suffix_start {
let offset_from_end = old_text.len() - old_cursor_byte;
return new_text.len().saturating_sub(offset_from_end);
}
new_suffix_start
}
pub fn get_node_text_content(node: &NodeData) -> Option<&str> {
if let crate::dom::NodeType::Text(ref text) = node.get_node_type() {
Some(text.as_str())
} else {
None
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TextChange {
pub old_text: String,
pub new_text: String,
}
#[derive(Debug, Clone, Default)]
pub struct NodeChangeReport {
pub change_set: NodeChangeSet,
pub relayout_scope: RelayoutScope,
pub changed_css_properties: Vec<CssPropertyType>,
pub text_change: Option<TextChange>,
}
impl NodeChangeReport {
pub fn needs_layout(&self) -> bool {
self.change_set.needs_layout() || self.relayout_scope > RelayoutScope::None
}
pub fn needs_paint(&self) -> bool {
self.change_set.needs_paint()
}
pub fn is_visually_unchanged(&self) -> bool {
self.change_set.is_visually_unchanged() && self.relayout_scope == RelayoutScope::None
}
}
#[derive(Debug, Clone, Default)]
pub struct ChangeAccumulator {
pub per_node: BTreeMap<NodeId, NodeChangeReport>,
pub max_scope: RelayoutScope,
pub mounted_nodes: Vec<NodeId>,
pub unmounted_nodes: Vec<NodeId>,
}
impl ChangeAccumulator {
pub fn new() -> Self {
Self::default()
}
pub fn is_empty(&self) -> bool {
self.per_node.is_empty() && self.mounted_nodes.is_empty() && self.unmounted_nodes.is_empty()
}
pub fn needs_layout(&self) -> bool {
self.max_scope > RelayoutScope::None
|| !self.mounted_nodes.is_empty()
|| self.per_node.values().any(|r| r.needs_layout())
}
pub fn needs_paint_only(&self) -> bool {
!self.needs_layout() && self.per_node.values().any(|r| r.needs_paint())
}
pub fn is_visually_unchanged(&self) -> bool {
self.mounted_nodes.is_empty()
&& self.unmounted_nodes.is_empty()
&& self.max_scope == RelayoutScope::None
&& self.per_node.values().all(|r| r.is_visually_unchanged())
}
pub fn add_dom_change(
&mut self,
new_node_id: NodeId,
change_set: NodeChangeSet,
relayout_scope: RelayoutScope,
text_change: Option<TextChange>,
changed_css_properties: Vec<CssPropertyType>,
) {
if relayout_scope > self.max_scope {
self.max_scope = relayout_scope;
}
let report = self.per_node.entry(new_node_id).or_default();
report.change_set |= change_set;
if relayout_scope > report.relayout_scope {
report.relayout_scope = relayout_scope;
}
if text_change.is_some() {
report.text_change = text_change;
}
report.changed_css_properties.extend(changed_css_properties);
}
pub fn add_text_change(
&mut self,
node_id: NodeId,
old_text: String,
new_text: String,
) {
let scope = RelayoutScope::IfcOnly;
if scope > self.max_scope {
self.max_scope = scope;
}
let report = self.per_node.entry(node_id).or_default();
report.change_set.insert(NodeChangeSet::TEXT_CONTENT);
if scope > report.relayout_scope {
report.relayout_scope = scope;
}
report.text_change = Some(TextChange { old_text, new_text });
}
pub fn add_css_change(
&mut self,
node_id: NodeId,
prop_type: CssPropertyType,
scope: RelayoutScope,
) {
if scope > self.max_scope {
self.max_scope = scope;
}
let report = self.per_node.entry(node_id).or_default();
if scope > RelayoutScope::None {
report.change_set.insert(NodeChangeSet::INLINE_STYLE_LAYOUT);
} else {
report.change_set.insert(NodeChangeSet::INLINE_STYLE_PAINT);
}
if scope > report.relayout_scope {
report.relayout_scope = scope;
}
report.changed_css_properties.push(prop_type);
}
pub fn add_image_change(
&mut self,
node_id: NodeId,
scope: RelayoutScope,
) {
if scope > self.max_scope {
self.max_scope = scope;
}
let report = self.per_node.entry(node_id).or_default();
report.change_set.insert(NodeChangeSet::IMAGE_CHANGED);
if scope > report.relayout_scope {
report.relayout_scope = scope;
}
}
pub fn add_mount(&mut self, node_id: NodeId) {
self.mounted_nodes.push(node_id);
}
pub fn add_unmount(&mut self, node_id: NodeId) {
self.unmounted_nodes.push(node_id);
}
pub fn merge_restyle_result(&mut self, restyle: &crate::styled_dom::RestyleResult) {
for (node_id, changed_props) in &restyle.changed_nodes {
for changed in changed_props {
let prop_type = changed.current_prop.get_type();
let scope = prop_type.relayout_scope(true); self.add_css_change(*node_id, prop_type, scope);
}
}
}
pub fn merge_extended_diff(
&mut self,
extended: &ExtendedDiffResult,
old_node_data: &[NodeData],
new_node_data: &[NodeData],
) {
for &(old_id, new_id, ref change_set) in &extended.node_changes {
if change_set.is_empty() {
continue;
}
let scope = self.classify_change_scope(change_set, new_node_data, new_id);
let text_change = if change_set.contains(NodeChangeSet::TEXT_CONTENT) {
let old_text = get_node_text_content(&old_node_data[old_id.index()])
.unwrap_or("")
.to_string();
let new_text = get_node_text_content(&new_node_data[new_id.index()])
.unwrap_or("")
.to_string();
Some(TextChange { old_text, new_text })
} else {
None
};
self.add_dom_change(new_id, *change_set, scope, text_change, Vec::new());
}
let matched_new: alloc::collections::BTreeSet<usize> = extended
.diff
.node_moves
.iter()
.map(|m| m.new_node_id.index())
.collect();
for idx in 0..new_node_data.len() {
if !matched_new.contains(&idx) {
self.add_mount(NodeId::new(idx));
}
}
let matched_old: alloc::collections::BTreeSet<usize> = extended
.diff
.node_moves
.iter()
.map(|m| m.old_node_id.index())
.collect();
for idx in 0..old_node_data.len() {
if !matched_old.contains(&idx) {
self.add_unmount(NodeId::new(idx));
}
}
}
fn classify_change_scope(
&self,
change_set: &NodeChangeSet,
new_node_data: &[NodeData],
new_node_id: NodeId,
) -> RelayoutScope {
if change_set.contains(NodeChangeSet::NODE_TYPE_CHANGED)
|| change_set.contains(NodeChangeSet::CHILDREN_CHANGED)
{
return RelayoutScope::Full;
}
if change_set.contains(NodeChangeSet::IDS_AND_CLASSES) {
return RelayoutScope::Full;
}
if change_set.contains(NodeChangeSet::INLINE_STYLE_LAYOUT) {
let new_node = &new_node_data[new_node_id.index()];
let mut max_scope = RelayoutScope::None;
for (prop, _conds) in new_node.style.iter_inline_properties() {
let scope = prop.get_type().relayout_scope(true);
if scope > max_scope {
max_scope = scope;
}
}
return if max_scope == RelayoutScope::None {
RelayoutScope::SizingOnly } else {
max_scope
};
}
if change_set.contains(NodeChangeSet::TEXT_CONTENT) {
return RelayoutScope::IfcOnly;
}
if change_set.contains(NodeChangeSet::IMAGE_CHANGED) {
return RelayoutScope::SizingOnly;
}
if change_set.contains(NodeChangeSet::CONTENTEDITABLE) {
return RelayoutScope::SizingOnly;
}
if change_set.intersects(NodeChangeSet::AFFECTS_PAINT) {
return RelayoutScope::None;
}
RelayoutScope::None
}
}
pub fn reconcile_dom_with_changes(
old_node_data: &[NodeData],
new_node_data: &[NodeData],
old_hierarchy: &[NodeHierarchyItem],
new_hierarchy: &[NodeHierarchyItem],
old_styled_nodes: Option<&[StyledNodeState]>,
new_styled_nodes: Option<&[StyledNodeState]>,
old_layout: &OrderedMap<NodeId, LogicalRect>,
new_layout: &OrderedMap<NodeId, LogicalRect>,
dom_id: DomId,
timestamp: Instant,
) -> ExtendedDiffResult {
let diff = reconcile_dom(
old_node_data,
new_node_data,
old_hierarchy,
new_hierarchy,
old_layout,
new_layout,
dom_id,
timestamp,
);
let mut node_changes = Vec::new();
for node_move in &diff.node_moves {
let old_nd = &old_node_data[node_move.old_node_id.index()];
let new_nd = &new_node_data[node_move.new_node_id.index()];
let old_state = old_styled_nodes.and_then(|s| s.get(node_move.old_node_id.index()));
let new_state = new_styled_nodes.and_then(|s| s.get(node_move.new_node_id.index()));
let changes = compute_node_changes(old_nd, new_nd, old_state, new_state);
node_changes.push((node_move.old_node_id, node_move.new_node_id, changes));
}
ExtendedDiffResult { diff, node_changes }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct NodeDataFingerprint {
pub content_hash: u64,
pub state_hash: u64,
pub inline_css_hash: u64,
pub ids_classes_hash: u64,
pub callbacks_hash: u64,
pub attrs_hash: u64,
}
impl Default for NodeDataFingerprint {
fn default() -> Self {
Self {
content_hash: 0,
state_hash: 0,
inline_css_hash: 0,
ids_classes_hash: 0,
callbacks_hash: 0,
attrs_hash: 0,
}
}
}
impl NodeDataFingerprint {
pub fn compute(node: &NodeData, styled_state: Option<&StyledNodeState>) -> Self {
use std::hash::Hasher;
use core::hash::Hash;
let content_hash = {
let mut h = std::hash::DefaultHasher::new();
node.get_node_type().hash(&mut h);
h.finish()
};
let state_hash = {
let mut h = std::hash::DefaultHasher::new();
if let Some(state) = styled_state {
state.hash(&mut h);
}
h.finish()
};
let inline_css_hash = {
let mut h = std::hash::DefaultHasher::new();
for (prop, conds) in node.style.iter_inline_properties() {
prop.hash(&mut h);
conds.as_slice().len().hash(&mut h);
}
h.finish()
};
let ids_classes_hash = {
let mut h = std::hash::DefaultHasher::new();
for attr in node.attributes().as_ref().iter() {
match attr {
crate::dom::AttributeType::Id(s) => {
crate::dom::IdOrClass::Id(s.clone()).hash(&mut h);
}
crate::dom::AttributeType::Class(s) => {
crate::dom::IdOrClass::Class(s.clone()).hash(&mut h);
}
_ => {}
}
}
h.finish()
};
let callbacks_hash = {
let mut h = std::hash::DefaultHasher::new();
for cb in node.callbacks.as_ref().iter() {
cb.event.hash(&mut h);
cb.callback.hash(&mut h);
}
h.finish()
};
let attrs_hash = {
let mut h = std::hash::DefaultHasher::new();
node.is_contenteditable().hash(&mut h);
node.flags.hash(&mut h);
node.get_dataset().hash(&mut h);
h.finish()
};
Self {
content_hash,
state_hash,
inline_css_hash,
ids_classes_hash,
callbacks_hash,
attrs_hash,
}
}
pub fn diff(&self, other: &NodeDataFingerprint) -> NodeChangeSet {
let mut changes = NodeChangeSet::empty();
if self.content_hash != other.content_hash {
changes.insert(NodeChangeSet::TEXT_CONTENT);
changes.insert(NodeChangeSet::IMAGE_CHANGED);
}
if self.state_hash != other.state_hash {
changes.insert(NodeChangeSet::STYLED_STATE);
}
if self.inline_css_hash != other.inline_css_hash {
changes.insert(NodeChangeSet::INLINE_STYLE_LAYOUT);
}
if self.ids_classes_hash != other.ids_classes_hash {
changes.insert(NodeChangeSet::IDS_AND_CLASSES);
}
if self.callbacks_hash != other.callbacks_hash {
changes.insert(NodeChangeSet::CALLBACKS);
}
if self.attrs_hash != other.attrs_hash {
changes.insert(NodeChangeSet::TAB_INDEX);
changes.insert(NodeChangeSet::CONTENTEDITABLE);
}
changes
}
pub fn is_identical(&self, other: &NodeDataFingerprint) -> bool {
self == other
}
pub fn might_affect_layout(&self, other: &NodeDataFingerprint) -> bool {
self.content_hash != other.content_hash
|| self.inline_css_hash != other.inline_css_hash
|| self.ids_classes_hash != other.ids_classes_hash
|| self.attrs_hash != other.attrs_hash
}
pub fn might_affect_visuals(&self, other: &NodeDataFingerprint) -> bool {
self.content_hash != other.content_hash
|| self.state_hash != other.state_hash
|| self.inline_css_hash != other.inline_css_hash
|| self.ids_classes_hash != other.ids_classes_hash
}
}