1use crate::tree::{WidgetId, WidgetNode, WidgetTree};
21use std::collections::HashSet;
22
23#[derive(Clone, Debug, PartialEq)]
25pub enum DiffOp {
26 Insert {
29 id: WidgetId,
31 parent: WidgetId,
33 index: usize,
35 },
36 Remove {
38 id: WidgetId,
40 },
41 Update {
43 id: WidgetId,
45 },
46 Move {
48 id: WidgetId,
50 parent: WidgetId,
52 index: usize,
54 },
55}
56
57pub fn diff(old: &WidgetTree, new: &WidgetTree) -> Vec<DiffOp> {
62 let mut ops = Vec::new();
63
64 let old_ids: HashSet<WidgetId> = collect_ids(old);
65 let new_ids: HashSet<WidgetId> = collect_ids(new);
66
67 for &id in old_ids.iter() {
70 if !new_ids.contains(&id) {
71 let ancestor_removed = ancestor_chain(old, id)
73 .into_iter()
74 .any(|anc| anc != id && !new_ids.contains(&anc));
75 if !ancestor_removed {
76 ops.push(DiffOp::Remove { id });
77 }
78 }
79 }
80
81 let mut visit_order = Vec::new();
84 new.walk_dfs(|node, _| visit_order.push(node.id));
85
86 for id in visit_order {
87 let new_node = match new.get(id) {
88 Some(n) => n,
89 None => continue,
90 };
91
92 match old.get(id) {
93 Some(old_node) => {
95 if node_changed(old_node, new_node) {
96 ops.push(DiffOp::Update { id });
97 }
98 reconcile_children(
99 id,
100 &old_node.children,
101 &new_node.children,
102 &old_ids,
103 &mut ops,
104 );
105 }
106 None => {
110 }
113 }
114 }
115
116 ops
117}
118
119fn reconcile_children(
121 parent: WidgetId,
122 old_children: &[WidgetId],
123 new_children: &[WidgetId],
124 old_ids: &HashSet<WidgetId>,
125 ops: &mut Vec<DiffOp>,
126) {
127 let lcs = longest_common_subsequence(old_children, new_children);
131 let kept: HashSet<WidgetId> = lcs.iter().copied().collect();
132
133 for (index, &child) in new_children.iter().enumerate() {
134 if kept.contains(&child) {
135 continue;
137 }
138 if old_ids.contains(&child) {
139 ops.push(DiffOp::Move {
141 id: child,
142 parent,
143 index,
144 });
145 } else {
146 ops.push(DiffOp::Insert {
148 id: child,
149 parent,
150 index,
151 });
152 }
153 }
154}
155
156fn node_changed(old: &WidgetNode, new: &WidgetNode) -> bool {
160 old.rect != new.rect
161 || old.z_index != new.z_index
162 || old.hit_testable != new.hit_testable
163 || old.focusable != new.focusable
164 || old.clip_rect != new.clip_rect
165 || old.label != new.label
166}
167
168fn collect_ids(tree: &WidgetTree) -> HashSet<WidgetId> {
170 let mut ids = HashSet::new();
171 tree.walk_dfs(|node, _| {
172 ids.insert(node.id);
173 });
174 ids
175}
176
177fn ancestor_chain(tree: &WidgetTree, id: WidgetId) -> Vec<WidgetId> {
179 let mut chain = Vec::new();
180 let mut cur = tree.get(id);
181 while let Some(node) = cur {
182 chain.push(node.id);
183 cur = node.parent.and_then(|p| tree.get(p));
184 }
185 chain
186}
187
188fn longest_common_subsequence(a: &[WidgetId], b: &[WidgetId]) -> Vec<WidgetId> {
194 let n = a.len();
195 let m = b.len();
196 if n == 0 || m == 0 {
197 return Vec::new();
198 }
199 let mut dp = vec![vec![0u32; m + 1]; n + 1];
201 for i in (0..n).rev() {
202 for j in (0..m).rev() {
203 dp[i][j] = if a[i] == b[j] {
204 dp[i + 1][j + 1] + 1
205 } else {
206 dp[i + 1][j].max(dp[i][j + 1])
207 };
208 }
209 }
210 let mut result = Vec::new();
212 let (mut i, mut j) = (0usize, 0usize);
213 while i < n && j < m {
214 if a[i] == b[j] {
215 result.push(a[i]);
216 i += 1;
217 j += 1;
218 } else if dp[i + 1][j] >= dp[i][j + 1] {
219 i += 1;
220 } else {
221 j += 1;
222 }
223 }
224 result
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230 use crate::geometry::Rect;
231
232 fn r(x: f32) -> Rect {
233 Rect::new(x, 0.0, 10.0, 10.0)
234 }
235
236 #[test]
237 fn lcs_basic() {
238 let a = [WidgetId(1), WidgetId(2), WidgetId(3), WidgetId(4)];
239 let b = [WidgetId(2), WidgetId(4), WidgetId(1), WidgetId(3)];
240 let lcs = longest_common_subsequence(&a, &b);
241 assert_eq!(lcs.len(), 2);
243 }
244
245 #[test]
246 fn identical_trees_yield_no_ops() {
247 let mut old = WidgetTree::new(r(0.0));
248 let a = old.insert(WidgetId::ROOT, r(1.0)).expect("root");
249 let _b = old.insert(WidgetId::ROOT, r(2.0)).expect("root");
250 let _c = old.insert(a, r(3.0)).expect("a");
251
252 let mut new = WidgetTree::new(r(0.0));
254 let a2 = new.insert(WidgetId::ROOT, r(1.0)).expect("root");
255 let _b2 = new.insert(WidgetId::ROOT, r(2.0)).expect("root");
256 let _c2 = new.insert(a2, r(3.0)).expect("a");
257
258 let ops = diff(&old, &new);
259 assert!(
260 ops.is_empty(),
261 "identical trees should diff to nothing: {ops:?}"
262 );
263 }
264
265 #[test]
266 fn detects_field_update() {
267 let mut old = WidgetTree::new(r(0.0));
268 let a = old.insert(WidgetId::ROOT, r(1.0)).expect("root");
269
270 let mut new = WidgetTree::new(r(0.0));
271 let a2 = new.insert(WidgetId::ROOT, r(1.0)).expect("root");
272 if let Some(n) = new.get_mut(a2) {
273 n.rect = r(99.0); }
275 assert_eq!(a, a2);
276
277 let ops = diff(&old, &new);
278 assert!(
279 ops.contains(&DiffOp::Update { id: a }),
280 "expected Update, got {ops:?}"
281 );
282 }
283
284 #[test]
285 fn detects_insert_and_remove() {
286 let mut old = WidgetTree::new(r(0.0));
288 let a = old.insert(WidgetId::ROOT, r(1.0)).expect("root");
289
290 let mut new = WidgetTree::new(r(0.0));
292 let a2 = new.insert(WidgetId::ROOT, r(1.0)).expect("root");
293 let b = new.insert(WidgetId::ROOT, r(2.0)).expect("root");
294 assert_eq!(a, a2);
295
296 let ops = diff(&old, &new);
297 assert!(
298 ops.iter()
299 .any(|o| matches!(o, DiffOp::Insert { id, parent, index }
300 if *id == b && *parent == WidgetId::ROOT && *index == 1)),
301 "expected Insert of b at index 1, got {ops:?}"
302 );
303
304 let ops_rev = diff(&new, &old);
306 assert!(
307 ops_rev.contains(&DiffOp::Remove { id: b }),
308 "expected Remove of b, got {ops_rev:?}"
309 );
310 }
311
312 #[test]
313 fn removed_subtree_emits_single_remove() {
314 let mut old = WidgetTree::new(r(0.0));
316 let a = old.insert(WidgetId::ROOT, r(1.0)).expect("root");
317 let a1 = old.insert(a, r(2.0)).expect("a");
318
319 let new = WidgetTree::new(r(0.0)); let ops = diff(&old, &new);
322 assert!(ops.contains(&DiffOp::Remove { id: a }), "got {ops:?}");
324 assert!(
325 !ops.contains(&DiffOp::Remove { id: a1 }),
326 "child should not get its own Remove: {ops:?}"
327 );
328 }
329
330 #[test]
331 fn reorder_emits_minimal_moves() {
332 let mut old = WidgetTree::new(r(0.0));
334 let c1 = old.insert(WidgetId::ROOT, r(1.0)).expect("root");
335 let c2 = old.insert(WidgetId::ROOT, r(2.0)).expect("root");
336 let c3 = old.insert(WidgetId::ROOT, r(3.0)).expect("root");
337
338 let mut new = WidgetTree::new(r(0.0));
341 let _ = new.insert(WidgetId::ROOT, r(1.0)).expect("root"); let _ = new.insert(WidgetId::ROOT, r(2.0)).expect("root"); let _ = new.insert(WidgetId::ROOT, r(3.0)).expect("root"); if let Some(root) = new.get_mut(WidgetId::ROOT) {
345 root.children = vec![c3, c1, c2];
346 }
347
348 let ops = diff(&old, &new);
349 let moves: Vec<_> = ops
351 .iter()
352 .filter(|o| matches!(o, DiffOp::Move { .. }))
353 .collect();
354 assert_eq!(moves.len(), 1, "exactly one move expected, got {ops:?}");
355 assert!(
356 matches!(moves[0], DiffOp::Move { id, index, .. } if *id == c3 && *index == 0),
357 "c3 should move to index 0, got {:?}",
358 moves[0]
359 );
360 }
361}