1use std::fmt;
27use std::ops::Range;
28use std::sync::Arc;
29use std::time::Duration;
30
31use rich::Segment;
32
33use super::{Change, Frame};
34
35pub trait Graphic: Send + Sync + fmt::Debug {
37 fn key(&self) -> u64;
39
40 fn draw(&self, frame: usize) -> String;
44
45 fn in_cells(&self) -> bool {
49 false
50 }
51}
52
53#[derive(Clone, Debug)]
56pub struct Placement {
57 pub row: usize,
58 pub column: usize,
59 pub cols: usize,
60 pub rows: usize,
61 pub graphic: Arc<dyn Graphic>,
62 pub frame: usize,
63}
64
65impl Placement {
66 pub fn same(&self, other: &Placement) -> bool {
68 self.row == other.row
69 && self.column == other.column
70 && self.cols == other.cols
71 && self.rows == other.rows
72 && self.frame == other.frame
73 && self.graphic.key() == other.graphic.key()
74 }
75
76 pub fn columns(&self) -> Range<usize> {
78 self.column..self.column + self.cols
79 }
80
81 pub fn touched_by(&self, change: &Change) -> bool {
83 (self.row..self.row + self.rows).contains(&change.row)
84 && change.columns.start < self.column + self.cols
85 && self.column < change.columns.end
86 }
87}
88
89pub trait PlacementSource: Send + Sync {
91 fn prepare(&self, line: Vec<Segment>) -> Vec<Segment> {
95 line
96 }
97
98 fn placements(&self, frame: &Frame) -> Vec<Placement>;
100
101 fn next_change(&self) -> Option<Duration> {
104 None
105 }
106
107 fn release(&self, retained: &[Placement]) -> String {
111 let _ = retained;
112 String::new()
113 }
114}
115
116#[derive(Clone, Debug, Default)]
118pub struct GraphicsPlan {
119 pub repaint: Vec<(usize, Range<usize>)>,
122 pub draw: Vec<Placement>,
124}
125
126impl GraphicsPlan {
127 pub fn is_empty(&self) -> bool {
128 self.repaint.is_empty() && self.draw.is_empty()
129 }
130}
131
132pub fn plan(previous: &[Placement], next: &[Placement], changes: &[Change]) -> GraphicsPlan {
135 let mut out = GraphicsPlan::default();
136 for old in previous {
137 if old.graphic.in_cells() || next.iter().any(|new| new.same(old)) {
138 continue;
139 }
140 for row in old.row..old.row + old.rows {
142 let mut open = None;
143 for column in old.columns() {
144 let painted = changes
145 .iter()
146 .any(|change| change.row == row && change.columns.contains(&column));
147 match (painted, open) {
148 (false, None) => open = Some(column),
149 (true, Some(start)) => {
150 out.repaint.push((row, start..column));
151 open = None;
152 }
153 _ => {}
154 }
155 }
156 if let Some(start) = open {
157 out.repaint.push((row, start..old.column + old.cols));
158 }
159 }
160 }
161 for new in next {
162 let kept = previous.iter().any(|old| old.same(new));
163 let wiped = !new.graphic.in_cells()
164 && (changes.iter().any(|change| new.touched_by(change))
165 || out.repaint.iter().any(|(row, columns)| {
166 (new.row..new.row + new.rows).contains(row)
167 && columns.start < new.column + new.cols
168 && new.column < columns.end
169 }));
170 if !kept || wiped {
171 out.draw.push(new.clone());
172 }
173 }
174 out
175}
176
177impl Frame {
178 pub fn with_placements(mut self, placements: Vec<Placement>) -> Frame {
180 self.placements = placements;
181 self
182 }
183
184 pub fn set_placements(&mut self, placements: Vec<Placement>) {
186 self.placements = placements;
187 }
188
189 pub fn placements(&self) -> &[Placement] {
191 &self.placements
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[derive(Debug)]
200 struct Image(u64, bool);
201
202 impl Graphic for Image {
203 fn key(&self) -> u64 {
204 self.0
205 }
206 fn draw(&self, frame: usize) -> String {
207 format!("<img{}:{frame}>", self.0)
208 }
209 fn in_cells(&self) -> bool {
210 self.1
211 }
212 }
213
214 fn at(row: usize, column: usize, key: u64, frame: usize) -> Placement {
215 Placement {
216 row,
217 column,
218 cols: 2,
219 rows: 1,
220 graphic: Arc::new(Image(key, false)),
221 frame,
222 }
223 }
224
225 fn change(row: usize, columns: Range<usize>) -> Change {
226 Change { row, columns }
227 }
228
229 #[test]
230 fn unchanged_placements_draw_nothing() {
231 let placed = [at(0, 4, 1, 0)];
232 assert!(plan(&placed, &placed, &[]).is_empty());
233 assert!(plan(&placed, &placed, &[change(0, 0..2)]).is_empty());
235 }
236
237 #[test]
238 fn new_moved_animated_and_wiped_placements_are_drawn() {
239 let old = [at(0, 4, 1, 0)];
240 assert_eq!(plan(&[], &old, &[]).draw.len(), 1);
241 let moved = plan(&old, &[at(0, 6, 1, 0)], &[]);
242 assert_eq!(moved.draw.len(), 1);
243 assert_eq!(moved.repaint, vec![(0, 4..6)]);
245 assert_eq!(plan(&old, &[at(0, 4, 1, 1)], &[]).draw.len(), 1);
246 let wiped = plan(&old, &old, &[change(0, 5..9)]);
247 assert_eq!(wiped.draw.len(), 1);
248 assert!(wiped.repaint.is_empty());
249 }
250
251 #[test]
252 fn a_removed_overlay_repaints_only_cells_the_diff_did_not() {
253 let old = [at(1, 4, 1, 0)];
254 let out = plan(&old, &[], &[change(1, 3..5)]);
255 assert_eq!(out.repaint, vec![(1, 5..6)]);
256 assert!(out.draw.is_empty());
257 }
258
259 #[test]
260 fn graphics_in_cells_are_drawn_once_and_never_repainted() {
261 let kitty = Placement {
262 graphic: Arc::new(Image(9, true)),
263 ..at(0, 0, 9, 0)
264 };
265 assert_eq!(plan(&[], std::slice::from_ref(&kitty), &[]).draw.len(), 1);
266 let again = plan(
267 std::slice::from_ref(&kitty),
268 std::slice::from_ref(&kitty),
269 &[change(0, 0..2)],
270 );
271 assert!(again.is_empty());
272 assert!(plan(std::slice::from_ref(&kitty), &[], &[]).is_empty());
273 }
274}