1#![allow(dead_code)]
8
9use alloc::{collections::BTreeMap, vec::Vec};
10
11use super::events::NodeId;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
19pub enum DirtyFlag {
20 #[default]
22 Clean = 0,
23 PaintDirty = 1,
25 LayoutDirty = 2,
27 StyleDirty = 3,
29}
30
31pub struct DirtyTracker {
37 flags: BTreeMap<NodeId, DirtyFlag>,
39 parents: BTreeMap<NodeId, NodeId>,
41 dirty_count: usize,
43}
44
45impl Default for DirtyTracker {
46 fn default() -> Self {
47 Self::new()
48 }
49}
50
51impl DirtyTracker {
52 pub fn new() -> Self {
53 Self {
54 flags: BTreeMap::new(),
55 parents: BTreeMap::new(),
56 dirty_count: 0,
57 }
58 }
59
60 pub(crate) fn set_parent(&mut self, child: NodeId, parent: NodeId) {
62 self.parents.insert(child, parent);
63 }
64
65 pub(crate) fn remove_node(&mut self, node: NodeId) {
67 if let Some(flag) = self.flags.remove(&node) {
68 if flag != DirtyFlag::Clean {
69 self.dirty_count = self.dirty_count.saturating_sub(1);
70 }
71 }
72 self.parents.remove(&node);
73 }
74
75 pub(crate) fn mark_dirty(&mut self, node: NodeId, flag: DirtyFlag) {
78 if flag == DirtyFlag::Clean {
79 return;
80 }
81
82 let current = self.flags.get(&node).copied().unwrap_or(DirtyFlag::Clean);
84 if flag > current {
85 if current == DirtyFlag::Clean {
86 self.dirty_count += 1;
87 }
88 self.flags.insert(node, flag);
89 }
90
91 if flag >= DirtyFlag::LayoutDirty {
93 let mut current_node = node;
94 while let Some(&parent) = self.parents.get(¤t_node) {
95 let parent_flag = self.flags.get(&parent).copied().unwrap_or(DirtyFlag::Clean);
96 if parent_flag >= DirtyFlag::LayoutDirty {
97 break; }
99 if parent_flag == DirtyFlag::Clean {
100 self.dirty_count += 1;
101 }
102 self.flags.insert(parent, DirtyFlag::LayoutDirty);
103 current_node = parent;
104 }
105 }
106 }
107
108 pub(crate) fn mark_style_dirty(&mut self, node: NodeId) {
110 self.mark_dirty(node, DirtyFlag::StyleDirty);
111 }
112
113 pub(crate) fn mark_layout_dirty(&mut self, node: NodeId) {
115 self.mark_dirty(node, DirtyFlag::LayoutDirty);
116 }
117
118 pub(crate) fn mark_paint_dirty(&mut self, node: NodeId) {
120 self.mark_dirty(node, DirtyFlag::PaintDirty);
121 }
122
123 pub(crate) fn get_flag(&self, node: NodeId) -> DirtyFlag {
125 self.flags.get(&node).copied().unwrap_or(DirtyFlag::Clean)
126 }
127
128 pub(crate) fn needs_restyle(&self, node: NodeId) -> bool {
130 self.get_flag(node) >= DirtyFlag::StyleDirty
131 }
132
133 pub(crate) fn needs_relayout(&self, node: NodeId) -> bool {
135 self.get_flag(node) >= DirtyFlag::LayoutDirty
136 }
137
138 pub(crate) fn needs_repaint(&self, node: NodeId) -> bool {
140 self.get_flag(node) >= DirtyFlag::PaintDirty
141 }
142
143 pub(crate) fn clear(&mut self, node: NodeId) {
145 if let Some(flag) = self.flags.get_mut(&node) {
146 if *flag != DirtyFlag::Clean {
147 *flag = DirtyFlag::Clean;
148 self.dirty_count = self.dirty_count.saturating_sub(1);
149 }
150 }
151 }
152
153 pub(crate) fn clear_all(&mut self) {
155 for flag in self.flags.values_mut() {
156 *flag = DirtyFlag::Clean;
157 }
158 self.dirty_count = 0;
159 }
160
161 pub(crate) fn has_dirty_nodes(&self) -> bool {
163 self.dirty_count > 0
164 }
165
166 pub(crate) fn dirty_node_count(&self) -> usize {
168 self.dirty_count
169 }
170
171 pub(crate) fn dirty_nodes(&self, min_flag: DirtyFlag) -> Vec<NodeId> {
173 self.flags
174 .iter()
175 .filter(|(_, &flag)| flag >= min_flag)
176 .map(|(&node, _)| node)
177 .collect()
178 }
179
180 pub(crate) fn highest_dirty_ancestor(&self, node: NodeId, min_flag: DirtyFlag) -> NodeId {
183 let mut highest = node;
184 let mut current = node;
185 while let Some(&parent) = self.parents.get(¤t) {
186 if self.get_flag(parent) >= min_flag {
187 highest = parent;
188 }
189 current = parent;
190 }
191 highest
192 }
193}
194
195#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
201pub struct DamageRegion {
202 pub x: i32,
203 pub y: i32,
204 pub width: i32,
205 pub height: i32,
206}
207
208impl DamageRegion {
209 pub fn new(x: i32, y: i32, width: i32, height: i32) -> Self {
210 Self {
211 x,
212 y,
213 width: width.max(0),
214 height: height.max(0),
215 }
216 }
217
218 pub(crate) fn is_empty(&self) -> bool {
220 self.width <= 0 || self.height <= 0
221 }
222
223 pub(crate) fn right(&self) -> i32 {
225 self.x + self.width
226 }
227
228 pub(crate) fn bottom(&self) -> i32 {
230 self.y + self.height
231 }
232
233 pub(crate) fn area(&self) -> i64 {
235 self.width as i64 * self.height as i64
236 }
237
238 pub(crate) fn union(&self, other: &DamageRegion) -> DamageRegion {
240 if self.is_empty() {
241 return *other;
242 }
243 if other.is_empty() {
244 return *self;
245 }
246 let x = self.x.min(other.x);
247 let y = self.y.min(other.y);
248 let r = self.right().max(other.right());
249 let b = self.bottom().max(other.bottom());
250 DamageRegion::new(x, y, r - x, b - y)
251 }
252
253 pub(crate) fn intersect(&self, other: &DamageRegion) -> DamageRegion {
255 let x = self.x.max(other.x);
256 let y = self.y.max(other.y);
257 let r = self.right().min(other.right());
258 let b = self.bottom().min(other.bottom());
259 if r <= x || b <= y {
260 DamageRegion::default()
261 } else {
262 DamageRegion::new(x, y, r - x, b - y)
263 }
264 }
265
266 pub(crate) fn contains(&self, other: &DamageRegion) -> bool {
268 self.x <= other.x
269 && self.y <= other.y
270 && self.right() >= other.right()
271 && self.bottom() >= other.bottom()
272 }
273
274 pub(crate) fn contains_point(&self, px: i32, py: i32) -> bool {
276 px >= self.x && px < self.right() && py >= self.y && py < self.bottom()
277 }
278}
279
280pub struct DamageList {
286 regions: Vec<DamageRegion>,
288 bounds: DamageRegion,
290 max_regions: usize,
292}
293
294impl Default for DamageList {
295 fn default() -> Self {
296 Self::new()
297 }
298}
299
300impl DamageList {
301 pub fn new() -> Self {
302 Self {
303 regions: Vec::new(),
304 bounds: DamageRegion::default(),
305 max_regions: 32,
306 }
307 }
308
309 pub(crate) fn add(&mut self, region: DamageRegion) {
311 if region.is_empty() {
312 return;
313 }
314 self.bounds = self.bounds.union(®ion);
315 self.regions.push(region);
316
317 if self.regions.len() > self.max_regions {
319 self.merge_all();
320 }
321 }
322
323 pub(crate) fn merge_all(&mut self) {
325 if !self.regions.is_empty() {
326 self.regions.clear();
327 self.regions.push(self.bounds);
328 }
329 }
330
331 pub(crate) fn bounding_box(&self) -> DamageRegion {
333 self.bounds
334 }
335
336 pub(crate) fn regions(&self) -> &[DamageRegion] {
338 &self.regions
339 }
340
341 pub(crate) fn has_damage(&self) -> bool {
343 !self.regions.is_empty()
344 }
345
346 pub(crate) fn clear(&mut self) {
348 self.regions.clear();
349 self.bounds = DamageRegion::default();
350 }
351
352 pub(crate) fn overlaps(&self, region: &DamageRegion) -> bool {
354 if self.bounds.intersect(region).is_empty() {
356 return false;
357 }
358 self.regions.iter().any(|r| !r.intersect(region).is_empty())
359 }
360}
361
362pub struct IncrementalLayout {
368 pub tracker: DirtyTracker,
370 pub damage: DamageList,
372 node_rects: BTreeMap<NodeId, DamageRegion>,
374}
375
376impl Default for IncrementalLayout {
377 fn default() -> Self {
378 Self::new()
379 }
380}
381
382impl IncrementalLayout {
383 pub fn new() -> Self {
384 Self {
385 tracker: DirtyTracker::new(),
386 damage: DamageList::new(),
387 node_rects: BTreeMap::new(),
388 }
389 }
390
391 pub(crate) fn set_node_rect(&mut self, node: NodeId, x: i32, y: i32, w: i32, h: i32) {
393 self.node_rects.insert(node, DamageRegion::new(x, y, w, h));
394 }
395
396 pub(crate) fn node_rect(&self, node: NodeId) -> Option<DamageRegion> {
398 self.node_rects.get(&node).copied()
399 }
400
401 pub(crate) fn remove_node_rect(&mut self, node: NodeId) {
403 self.node_rects.remove(&node);
404 }
405
406 pub(crate) fn restyle_roots(&self) -> Vec<NodeId> {
409 let dirty = self.tracker.dirty_nodes(DirtyFlag::StyleDirty);
410 let mut roots = Vec::new();
411 for &node in &dirty {
412 let root = self
413 .tracker
414 .highest_dirty_ancestor(node, DirtyFlag::StyleDirty);
415 if !roots.contains(&root) {
416 roots.push(root);
417 }
418 }
419 roots
420 }
421
422 pub(crate) fn relayout_roots(&self) -> Vec<NodeId> {
424 let dirty = self.tracker.dirty_nodes(DirtyFlag::LayoutDirty);
425 let mut roots = Vec::new();
426 for &node in &dirty {
427 let root = self
428 .tracker
429 .highest_dirty_ancestor(node, DirtyFlag::LayoutDirty);
430 if !roots.contains(&root) {
431 roots.push(root);
432 }
433 }
434 roots
435 }
436
437 pub(crate) fn compute_damage(&mut self) {
439 let paint_dirty = self.tracker.dirty_nodes(DirtyFlag::PaintDirty);
440 for &node in &paint_dirty {
441 if let Some(rect) = self.node_rects.get(&node) {
442 self.damage.add(*rect);
443 }
444 }
445 }
446
447 pub(crate) fn finish_update(&mut self) {
449 self.tracker.clear_all();
450 self.damage.clear();
451 }
452
453 pub(crate) fn flush(&mut self) -> Option<DamageRegion> {
456 self.compute_damage();
457 if self.damage.has_damage() {
458 let bbox = self.damage.bounding_box();
459 self.finish_update();
460 Some(bbox)
461 } else {
462 self.tracker.clear_all();
463 None
464 }
465 }
466}
467
468#[cfg(test)]
473mod tests {
474 #[allow(unused_imports)]
475 use alloc::vec;
476
477 use super::*;
478
479 #[test]
480 fn test_dirty_flag_ordering() {
481 assert!(DirtyFlag::Clean < DirtyFlag::PaintDirty);
482 assert!(DirtyFlag::PaintDirty < DirtyFlag::LayoutDirty);
483 assert!(DirtyFlag::LayoutDirty < DirtyFlag::StyleDirty);
484 }
485
486 #[test]
487 fn test_dirty_tracker_mark_and_get() {
488 let mut t = DirtyTracker::new();
489 assert_eq!(t.get_flag(1), DirtyFlag::Clean);
490 t.mark_paint_dirty(1);
491 assert_eq!(t.get_flag(1), DirtyFlag::PaintDirty);
492 assert!(t.needs_repaint(1));
493 assert!(!t.needs_relayout(1));
494 }
495
496 #[test]
497 fn test_dirty_upgrade() {
498 let mut t = DirtyTracker::new();
499 t.mark_paint_dirty(1);
500 t.mark_layout_dirty(1);
501 assert_eq!(t.get_flag(1), DirtyFlag::LayoutDirty);
502 t.mark_paint_dirty(1);
504 assert_eq!(t.get_flag(1), DirtyFlag::LayoutDirty);
505 }
506
507 #[test]
508 fn test_dirty_propagation() {
509 let mut t = DirtyTracker::new();
510 t.set_parent(1, 0);
511 t.set_parent(2, 1);
512 t.mark_layout_dirty(2);
513 assert!(t.needs_relayout(1));
515 assert!(t.needs_relayout(0));
516 }
517
518 #[test]
519 fn test_dirty_no_propagation_for_paint() {
520 let mut t = DirtyTracker::new();
521 t.set_parent(1, 0);
522 t.mark_paint_dirty(1);
523 assert!(!t.needs_repaint(0));
524 }
525
526 #[test]
527 fn test_dirty_clear() {
528 let mut t = DirtyTracker::new();
529 t.mark_style_dirty(1);
530 assert_eq!(t.dirty_node_count(), 1);
531 t.clear(1);
532 assert_eq!(t.get_flag(1), DirtyFlag::Clean);
533 assert_eq!(t.dirty_node_count(), 0);
534 }
535
536 #[test]
537 fn test_dirty_clear_all() {
538 let mut t = DirtyTracker::new();
539 t.mark_paint_dirty(1);
540 t.mark_layout_dirty(2);
541 t.clear_all();
542 assert!(!t.has_dirty_nodes());
543 }
544
545 #[test]
546 fn test_dirty_nodes_filter() {
547 let mut t = DirtyTracker::new();
548 t.mark_paint_dirty(1);
549 t.mark_layout_dirty(2);
550 t.mark_style_dirty(3);
551 let layout_dirty = t.dirty_nodes(DirtyFlag::LayoutDirty);
552 assert_eq!(layout_dirty.len(), 2); assert!(layout_dirty.contains(&2));
554 assert!(layout_dirty.contains(&3));
555 }
556
557 #[test]
558 fn test_highest_dirty_ancestor() {
559 let mut t = DirtyTracker::new();
560 t.set_parent(1, 0);
561 t.set_parent(2, 1);
562 t.mark_layout_dirty(2);
563 let root = t.highest_dirty_ancestor(2, DirtyFlag::LayoutDirty);
564 assert_eq!(root, 0);
565 }
566
567 #[test]
568 fn test_damage_region_basic() {
569 let r = DamageRegion::new(10, 20, 100, 50);
570 assert_eq!(r.right(), 110);
571 assert_eq!(r.bottom(), 70);
572 assert!(!r.is_empty());
573 assert_eq!(r.area(), 5000);
574 }
575
576 #[test]
577 fn test_damage_region_empty() {
578 let r = DamageRegion::new(0, 0, 0, 0);
579 assert!(r.is_empty());
580 }
581
582 #[test]
583 fn test_damage_union() {
584 let a = DamageRegion::new(0, 0, 50, 50);
585 let b = DamageRegion::new(30, 30, 50, 50);
586 let u = a.union(&b);
587 assert_eq!(u.x, 0);
588 assert_eq!(u.y, 0);
589 assert_eq!(u.width, 80);
590 assert_eq!(u.height, 80);
591 }
592
593 #[test]
594 fn test_damage_intersect() {
595 let a = DamageRegion::new(0, 0, 50, 50);
596 let b = DamageRegion::new(30, 30, 50, 50);
597 let i = a.intersect(&b);
598 assert_eq!(i.x, 30);
599 assert_eq!(i.y, 30);
600 assert_eq!(i.width, 20);
601 assert_eq!(i.height, 20);
602 }
603
604 #[test]
605 fn test_damage_intersect_disjoint() {
606 let a = DamageRegion::new(0, 0, 10, 10);
607 let b = DamageRegion::new(20, 20, 10, 10);
608 let i = a.intersect(&b);
609 assert!(i.is_empty());
610 }
611
612 #[test]
613 fn test_damage_contains() {
614 let a = DamageRegion::new(0, 0, 100, 100);
615 let b = DamageRegion::new(10, 10, 50, 50);
616 assert!(a.contains(&b));
617 assert!(!b.contains(&a));
618 }
619
620 #[test]
621 fn test_damage_contains_point() {
622 let r = DamageRegion::new(10, 10, 20, 20);
623 assert!(r.contains_point(15, 15));
624 assert!(!r.contains_point(5, 5));
625 assert!(!r.contains_point(30, 30));
626 }
627
628 #[test]
629 fn test_damage_list() {
630 let mut dl = DamageList::new();
631 assert!(!dl.has_damage());
632 dl.add(DamageRegion::new(0, 0, 10, 10));
633 dl.add(DamageRegion::new(50, 50, 20, 20));
634 assert!(dl.has_damage());
635 assert_eq!(dl.regions().len(), 2);
636 let bbox = dl.bounding_box();
637 assert_eq!(bbox.x, 0);
638 assert_eq!(bbox.width, 70);
639 }
640
641 #[test]
642 fn test_damage_list_clear() {
643 let mut dl = DamageList::new();
644 dl.add(DamageRegion::new(0, 0, 10, 10));
645 dl.clear();
646 assert!(!dl.has_damage());
647 }
648
649 #[test]
650 fn test_damage_list_overlaps() {
651 let mut dl = DamageList::new();
652 dl.add(DamageRegion::new(10, 10, 20, 20));
653 let query = DamageRegion::new(15, 15, 5, 5);
654 assert!(dl.overlaps(&query));
655 let query2 = DamageRegion::new(100, 100, 5, 5);
656 assert!(!dl.overlaps(&query2));
657 }
658
659 #[test]
660 fn test_incremental_layout_restyle_roots() {
661 let mut il = IncrementalLayout::new();
662 il.tracker.set_parent(1, 0);
663 il.tracker.set_parent(2, 1);
664 il.tracker.mark_style_dirty(2);
665 let roots = il.restyle_roots();
666 assert_eq!(roots, vec![2]);
669 }
670
671 #[test]
672 fn test_incremental_layout_flush() {
673 let mut il = IncrementalLayout::new();
674 il.tracker.mark_paint_dirty(1);
675 il.set_node_rect(1, 10, 10, 50, 50);
676 let bbox = il.flush();
677 assert!(bbox.is_some());
678 let bbox = bbox.unwrap();
679 assert_eq!(bbox.x, 10);
680 assert_eq!(bbox.width, 50);
681 assert!(!il.tracker.has_dirty_nodes());
682 }
683
684 #[test]
685 fn test_incremental_layout_no_damage() {
686 let mut il = IncrementalLayout::new();
687 let bbox = il.flush();
688 assert!(bbox.is_none());
689 }
690
691 #[test]
692 fn test_remove_node() {
693 let mut t = DirtyTracker::new();
694 t.mark_layout_dirty(5);
695 assert_eq!(t.dirty_node_count(), 1);
696 t.remove_node(5);
697 assert_eq!(t.dirty_node_count(), 0);
698 }
699}