Skip to main content

termgrid_core/
damage.rs

1//! Damage tracking for incremental terminal rendering.
2//!
3//! Coordinates are expressed in terminal **cells**.
4
5#[derive(Debug, Copy, Clone, PartialEq, Eq)]
6pub struct Rect {
7    pub x: u16,
8    pub y: u16,
9    pub w: u16,
10    pub h: u16,
11}
12
13impl Rect {
14    pub fn new(x: u16, y: u16, w: u16, h: u16) -> Self {
15        Self { x, y, w, h }
16    }
17
18    pub fn is_empty(&self) -> bool {
19        self.w == 0 || self.h == 0
20    }
21
22    pub fn right(&self) -> u16 {
23        self.x.saturating_add(self.w)
24    }
25
26    pub fn bottom(&self) -> u16 {
27        self.y.saturating_add(self.h)
28    }
29
30    pub fn intersects_or_touches(&self, other: &Rect) -> bool {
31        // Treat adjacency as mergeable to reduce rect count.
32        let ax1 = self.x;
33        let ay1 = self.y;
34        let ax2 = self.right();
35        let ay2 = self.bottom();
36
37        let bx1 = other.x;
38        let by1 = other.y;
39        let bx2 = other.right();
40        let by2 = other.bottom();
41
42        !(ax2 < bx1 || bx2 < ax1 || ay2 < by1 || by2 < ay1)
43    }
44
45    pub fn union(&self, other: &Rect) -> Rect {
46        let x1 = self.x.min(other.x);
47        let y1 = self.y.min(other.y);
48        let x2 = self.right().max(other.right());
49        let y2 = self.bottom().max(other.bottom());
50        Rect::new(x1, y1, x2.saturating_sub(x1), y2.saturating_sub(y1))
51    }
52}
53
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct Damage {
56    pub full_redraw: bool,
57    pub rects: Vec<Rect>,
58}
59
60impl Damage {
61    pub fn empty() -> Self {
62        Self {
63            full_redraw: false,
64            rects: Vec::new(),
65        }
66    }
67
68    pub fn full() -> Self {
69        Self {
70            full_redraw: true,
71            rects: Vec::new(),
72        }
73    }
74
75    pub fn push_rect(&mut self, rect: Rect, max_rects: usize) {
76        if self.full_redraw {
77            return;
78        }
79        if rect.is_empty() {
80            return;
81        }
82
83        self.rects.push(rect);
84        self.merge_in_place();
85
86        if self.rects.len() > max_rects {
87            self.full_redraw = true;
88            self.rects.clear();
89        }
90    }
91
92    fn merge_in_place(&mut self) {
93        // Small N; O(n^2) is fine and keeps behavior deterministic.
94        let mut out: Vec<Rect> = Vec::with_capacity(self.rects.len());
95        for r in self.rects.drain(..) {
96            let mut merged = r;
97            let mut i = 0;
98            while i < out.len() {
99                if merged.intersects_or_touches(&out[i]) {
100                    merged = merged.union(&out[i]);
101                    out.remove(i);
102                    i = 0;
103                } else {
104                    i += 1;
105                }
106            }
107            out.push(merged);
108        }
109        self.rects = out;
110    }
111}