1use super::super::{Grid, Pos, flat_index_to_xy};
5use crate::backend::DrawCell;
6use crate::color::Color;
7#[cfg(test)]
8use crate::color::Style;
9#[cfg(test)]
10use crate::tile::Tile;
11use crate::tile::TileFlags;
12use grixy::ops::GridWrite;
13
14impl Grid {
15 pub fn layers(&self) -> impl Iterator<Item = DrawCell<'_>> + '_ {
25 let width = usize::from(self.width);
26 (0..=self.max_layer)
27 .filter_map(move |id| self.layer(id).map(|lb| (id, lb)))
28 .flat_map(move |(id, lb)| {
29 lb.buf.as_ref().iter().enumerate().map(move |(i, tile)| {
30 let (x, y) = flat_index_to_xy(i, width);
31 DrawCell {
32 layer: id,
33 pos: Pos::new(x, y),
34 tile,
35 grapheme: lb.extra_for(i, tile),
36 tint: lb.tint_for(i, tile),
37 }
38 })
39 })
40 }
41
42 pub fn clear_all(&mut self) {
44 for layer in self.layers.iter_mut().flatten() {
45 layer.buf.clear();
46 layer.extras.clear();
47 }
48 }
49
50 pub(crate) fn flatten_into(&self, dst: &mut Self) {
78 debug_assert_eq!(
79 (self.width, self.height),
80 (dst.width, dst.height),
81 "flatten_into requires dst to have the same dimensions as self"
82 );
83 dst.has_spans |= self.has_spans;
84 let layer0 = self.layer0();
85 let cell_count = layer0.buf.as_ref().len();
86
87 let dst_layer0 = dst.layer0_mut();
91 dst_layer0.buf.as_mut().copy_from_slice(layer0.buf.as_ref());
92 dst_layer0.extras.clear();
93 for (&idx, extra) in &layer0.extras {
94 if layer0.buf.as_ref()[idx]
95 .flags
96 .contains(TileFlags::HAS_EXTRA)
97 {
98 dst_layer0.extras.insert(idx, extra.clone());
99 }
100 }
101
102 for id in 1..=self.max_layer {
104 let Some(lb) = self.layer(id) else {
105 continue;
106 };
107 let src_buf = lb.buf.as_ref();
108 debug_assert_eq!(src_buf.len(), cell_count);
109 let dst_layer0 = dst.layer0_mut();
110 for (idx, tile) in src_buf.iter().enumerate() {
111 if !tile.flags.contains(TileFlags::EMPTY) {
112 {
113 let out = &mut dst_layer0.buf.as_mut()[idx];
114 out.glyph = tile.glyph;
115 out.width = tile.width;
116 out.style.fg = tile.style.fg;
117 out.dx = tile.dx;
118 out.dy = tile.dy;
119 out.flags = tile.flags;
120 out.span_w = tile.span_w;
121 out.span_h = tile.span_h;
122 }
123 if tile.flags.contains(TileFlags::HAS_EXTRA) {
124 if let Some(extra) = lb.extra_entry_for(idx, tile) {
125 dst_layer0.extras.insert(idx, extra);
126 }
127 } else {
128 dst_layer0.extras.remove(&idx);
129 }
130 }
131 if tile.style.bg != Color::Default {
132 dst_layer0.buf.as_mut()[idx].style.bg = tile.style.bg;
133 }
134 }
135 }
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[cfg(feature = "egc")]
144 #[test]
145 fn clone_preserves_extra() {
146 let mut g = Grid::new(2, 2);
147 g.write_grapheme(0, 0, 0, "e\u{0301}", Style::default());
148 let cloned = g.clone();
149 assert_eq!(
150 crate::grid::grapheme_at(&cloned, 0, 0, 0),
151 Some("e\u{0301}")
152 );
153 }
154
155 #[cfg(feature = "egc")]
156 #[test]
157 fn flatten_into_carries_extra_from_higher_layer() {
158 let mut g = Grid::new(2, 2);
159 g.write_grapheme(1, 0, 0, "e\u{0301}", Style::default());
160 let mut flattened = Grid::new(2, 2);
161 g.flatten_into(&mut flattened);
162 assert_eq!(flattened[Pos::new(0, 0)].glyph, 'e');
163 assert_eq!(
164 crate::grid::grapheme_at(&flattened, 0, 0, 0),
165 Some("e\u{0301}")
166 );
167 }
168
169 #[test]
170 #[should_panic(expected = "same dimensions")]
171 fn flatten_into_mismatched_dimensions_panics() {
172 let g = Grid::new(2, 2);
173 let mut flattened = Grid::new(2, 3);
174 g.flatten_into(&mut flattened);
175 }
176
177 #[test]
178 fn flatten_into_single_layer_is_a_plain_copy() {
179 let mut g = Grid::new(2, 2);
180 g.put_tile(0, (0, 0), Tile::new('a', Style::default()));
181 g.put_tile(0, (1, 1), Tile::new('b', Style::default()));
182 let mut flattened = Grid::new(2, 2);
183 g.flatten_into(&mut flattened);
184 assert_eq!(flattened[Pos::new(0, 0)].glyph(), 'a');
185 assert_eq!(flattened[Pos::new(1, 1)].glyph(), 'b');
186 assert_eq!(flattened[Pos::new(1, 0)].glyph(), ' ');
187 }
188
189 #[test]
190 fn flatten_into_higher_layer_overwrites_glyph_and_fg_but_not_default_bg() {
191 let mut g = Grid::new(1, 1);
192 g.put_tile(
193 0,
194 (0, 0),
195 Tile::new('a', Style::new().fg(Color::BLACK).bg(Color::WHITE)),
196 );
197 g.put_tile(1, (0, 0), Tile::new('b', Style::new().fg(Color::WHITE)));
198
199 let mut flattened = Grid::new(1, 1);
200 g.flatten_into(&mut flattened);
201 let out = flattened[Pos::new(0, 0)];
202 assert_eq!(out.glyph(), 'b');
203 assert_eq!(out.style().fg, Color::WHITE);
204 assert_eq!(out.style().bg, Color::WHITE);
206 }
207
208 #[test]
209 fn flatten_into_empty_higher_layer_cell_is_transparent() {
210 let mut g = Grid::new(2, 1);
211 g.put_tile(0, (0, 0), Tile::new('a', Style::default()));
212 g.put_tile(0, (1, 0), Tile::new('b', Style::default()));
213 g.put_tile(1, (0, 0), Tile::new('c', Style::default()));
215
216 let mut flattened = Grid::new(2, 1);
217 g.flatten_into(&mut flattened);
218 assert_eq!(flattened[Pos::new(0, 0)].glyph(), 'c');
219 assert_eq!(flattened[Pos::new(1, 0)].glyph(), 'b');
221 }
222
223 #[test]
224 fn flatten_into_multi_layer_stale_dst_extra_is_cleared() {
225 let mut flattened = Grid::new(1, 1);
228 flattened.put_tile(0, (0, 0), Tile::new('z', Style::default()));
229
230 let g = Grid::new(1, 1);
231 g.flatten_into(&mut flattened);
232 assert_eq!(flattened[Pos::new(0, 0)].glyph(), ' ');
233 }
234
235 #[test]
236 fn flatten_into_carries_span() {
237 let mut grid = Grid::new(4, 4);
241 grid.write_span(2, 1, 1, &["C=", "[]"], Style::default())
242 .unwrap();
243
244 let mut flat = Grid::new(4, 4);
245 grid.flatten_into(&mut flat);
246
247 assert_eq!(flat[Pos::new(1, 1)].span(), (2, 2));
248 assert!(
249 flat[Pos::new(1, 1)]
250 .flags()
251 .contains(TileFlags::SPAN_ANCHOR)
252 );
253 assert_eq!(flat.span_owner(0, 2, 2), Some(Pos::new(1, 1)));
254 assert_eq!(flat[Pos::new(2, 2)].glyph(), ']');
255 }
256
257 #[test]
258 fn copy_layer_from_overwrites_a_cell_the_destination_wrote_this_frame() {
259 let mut src = Grid::new(4, 1);
262 src.put_tile(0, (0, 0), Tile::new('W', Style::default()));
263
264 let mut dst = Grid::new(4, 1);
265 dst.put_tile(0, (0, 0), Tile::new('W', Style::default()));
266 dst.put_tile(0, (2, 0), Tile::new('X', Style::default()));
267 dst.copy_layer_from(0, &src);
268
269 assert_eq!(dst[Pos::new(0, 0)].glyph(), 'W');
270 assert_eq!(dst[Pos::new(2, 0)].glyph(), ' ');
271 }
272
273 #[test]
274 fn copy_layer_from_preserves_span_flags_verbatim() {
275 let mut src = Grid::new(4, 4);
279 src.write_span(0, 0, 0, &["C=", "[]"], Style::default())
280 .unwrap();
281
282 let mut dst = Grid::new(4, 4);
283 dst.copy_layer_from(0, &src);
284
285 assert_eq!(dst[Pos::new(0, 0)].span(), (2, 2));
286 assert!(dst[Pos::new(0, 0)].flags().contains(TileFlags::SPAN_ANCHOR));
287 assert_eq!(dst.span_owner(0, 1, 1), Some(Pos::new(0, 0)));
288 assert_eq!(dst[Pos::new(1, 1)].glyph(), ']');
289 }
290
291 #[test]
292 fn copy_layer_from_clears_the_destination_when_the_source_layer_is_unallocated() {
293 let src = Grid::new(4, 1);
296
297 let mut dst = Grid::new(4, 1);
298 dst.put_tile(1, (0, 0), Tile::new('X', Style::default()));
299 dst.copy_layer_from(1, &src);
300
301 assert_eq!(dst.tile(1, (0, 0)), None);
302 }
303
304 #[test]
305 fn copy_layer_from_is_a_noop_when_neither_side_has_the_layer_allocated() {
306 let src = Grid::new(4, 1);
307 let mut dst = Grid::new(4, 1);
308 dst.copy_layer_from(1, &src);
309
310 assert_eq!(dst.tile(1, (0, 0)), None);
311 }
312
313 #[test]
314 fn copy_layer_from_lowers_max_layer_when_the_source_layer_is_unallocated() {
315 let src = Grid::new(4, 1);
319
320 let mut dst = Grid::new(4, 1);
321 dst.put_tile(5, (0, 0), Tile::new('X', Style::default()));
322 assert_eq!(dst.max_layer(), 5);
323
324 dst.copy_layer_from(5, &src);
325
326 assert_eq!(dst.max_layer(), 0);
327 assert_eq!(dst.tile(5, (0, 0)), None);
328 }
329}