Skip to main content

retroglyph_core/grid/
trait_impls.rs

1//! `Grid`'s trait impls for layer 0: [`Index`]/[`IndexMut`] by [`Pos`](crate::grid::Pos), and its
2//! [`Display`](fmt::Display)/[`Debug`](fmt::Debug) implementations.
3
4use super::{Grid, Pos, to_grixy_pos};
5#[cfg(test)]
6use crate::color::Style;
7use crate::tile::{Tile, TileFlags};
8use core::fmt;
9use core::ops::{Index, IndexMut};
10
11impl Index<Pos> for Grid {
12    type Output = Tile;
13
14    /// Reads the tile on layer 0 at `pos`.
15    ///
16    /// # Panics
17    ///
18    /// Panics if `pos` is outside the grid's `0..width` x `0..height` bounds. This is the
19    /// unchecked, layer-0-only counterpart to [`tile`](Self::tile), which instead returns `None`
20    /// on either an out-of-bounds `pos` or an unallocated layer; reach for `tile` when `pos`
21    /// isn't already known to be in bounds.
22    fn index(&self, pos: Pos) -> &Tile {
23        &self.layer0().buf[to_grixy_pos(pos)]
24    }
25}
26
27impl IndexMut<Pos> for Grid {
28    /// Mutably borrows the tile on layer 0 at `pos`.
29    ///
30    /// # Panics
31    ///
32    /// Panics if `pos` is outside the grid's `0..width` x `0..height` bounds, the same bound as
33    /// [`Index`]'s `index`. Reach for [`tile_mut`](Self::tile_mut) when `pos` isn't already known
34    /// to be in bounds; it returns `None` instead of panicking.
35    fn index_mut(&mut self, pos: Pos) -> &mut Tile {
36        let pos = to_grixy_pos(pos);
37        &mut self.layer0_mut().buf[pos]
38    }
39}
40
41// ---------------------------------------------------------------------------
42// Display / Debug: layer 0
43// ---------------------------------------------------------------------------
44
45/// Renders layer 0 only, one character per cell, with `·` in place of a plain space.
46impl fmt::Display for Grid {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        for y in 0..self.height() {
49            for x in 0..self.width() {
50                let tile = &self[Pos::new(x, y)];
51                let is_spacer = tile.flags.contains(TileFlags::WIDE_CHAR_SPACER);
52                let c = if is_spacer {
53                    ' ' // right half of a wide char, don't print twice
54                } else if tile.glyph == ' ' {
55                    '·' // empty cell marker
56                } else {
57                    tile.glyph
58                };
59                write!(f, "{c}")?;
60            }
61            writeln!(f)?;
62        }
63        Ok(())
64    }
65}
66
67/// Shows `width`, `height`, `max_layer`, and `has_spans`; the layer buffers themselves are
68/// omitted (see [`Display`](fmt::Display) for a rendering of layer 0).
69impl fmt::Debug for Grid {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        f.debug_struct("Grid")
72            .field("width", &self.width)
73            .field("height", &self.height)
74            .field("max_layer", &self.max_layer)
75            .field("has_spans", &self.has_spans)
76            .finish_non_exhaustive()
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    #[should_panic(expected = "index out of bounds")]
86    fn index_panics_out_of_bounds() {
87        let grid = Grid::new(10, 10);
88        let _ = &grid[Pos::new(0, 10)];
89    }
90
91    #[test]
92    fn index_by_pos_reads_back_the_written_glyph() {
93        let mut grid = Grid::new(5, 5);
94        let pos = Pos::new(2, 3);
95        grid[pos] = Tile::default().with_glyph('Z');
96        assert_eq!(grid[pos].glyph(), 'Z');
97    }
98
99    #[test]
100    fn display_renders_glyphs_row_major_with_a_middle_dot_for_empty_cells() {
101        let mut grid = Grid::new(3, 2);
102        grid.put_tile(0, (0, 0), Tile::default().with_glyph('A'));
103
104        let s = alloc::format!("{grid}");
105        assert_eq!(s, "A··\n···\n");
106    }
107
108    #[cfg(feature = "egc")]
109    #[test]
110    fn display_wide_char_spacer_renders_as_a_plain_space() {
111        // A wide char's right-half spacer cell prints as a plain space, not the wide
112        // char's own glyph repeated.
113        let mut grid = Grid::new(3, 1);
114        grid.write_grapheme(0, 0, 0, "\u{4e2d}", Style::default()); // wide (CJK)
115
116        let s = alloc::format!("{grid}");
117        assert_eq!(s, "\u{4e2d} \u{b7}\n");
118    }
119
120    #[test]
121    fn debug_reports_layer_and_span_state() {
122        let mut grid = Grid::new(3, 2);
123        grid.put_tile(2, (0, 0), Tile::default().with_glyph('A'));
124        grid.write_span(0, 0, 0, &["hi"], Style::default());
125
126        let s = alloc::format!("{grid:?}");
127        assert!(s.contains("width: 3"));
128        assert!(s.contains("height: 2"));
129        assert!(s.contains("max_layer: 2"));
130        assert!(s.contains("has_spans: true"));
131    }
132}