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