Skip to main content

termint/widgets/
grid.rs

1use crate::{
2    buffer::Buffer,
3    geometry::{Rect, Unit, Vec2},
4    widgets::cache::{Cache, GridCache},
5};
6
7use super::{widget::Widget, Element};
8
9/// A layout widget that arranges children in a grid specified by rows and
10/// columns.
11///
12/// Each row and column is defined by a [`Unit`], which you can read more about
13/// in its documentation.
14///
15/// Children can be placed by specifying their zero-based column and row
16/// indices.
17///
18/// # Example
19/// ```rust
20/// # use termint::{
21/// #     geometry::{Rect, Unit},
22/// #     widgets::{Grid, Widget},
23/// #     term::Term,
24/// # };
25/// # fn example() -> Result<(), termint::Error> {
26/// let mut grid = Grid::new(
27///     vec![Unit::Length(3), Unit::Length(5), Unit::Fill(1)],
28///     vec![Unit::Fill(1), Unit::Length(1), Unit::Fill(1)],
29/// );
30/// grid.push("Grid", 1, 1);
31///
32/// let mut term = Term::default();
33/// term.render(grid)?;
34/// # Ok(())
35/// # }
36/// ```
37#[derive(Debug, Default)]
38pub struct Grid {
39    children: Vec<GridChild>,
40    rows: Vec<Unit>,
41    cols: Vec<Unit>,
42}
43
44/// Internal struct representing a child widget in a specific grid cell.
45#[derive(Debug)]
46struct GridChild {
47    pub child: Element,
48    pub row: usize,
49    pub col: usize,
50}
51
52impl Grid {
53    /// Creates a new [`Grid`] from columns and rows specifications.
54    ///
55    /// Both `cols` and `rows` accept any iterable of types convertible into
56    /// [`Unit`].
57    ///
58    /// # Example
59    /// ```rust
60    /// # use termint::{
61    /// #     geometry::{Rect, Unit},
62    /// #     widgets::{Grid, Widget},
63    /// #     term::Term,
64    /// # };
65    /// let mut grid = Grid::new([3, 5, 3], [3, 1, 1]);
66    /// ```
67    #[must_use]
68    pub fn new<T1, T2>(cols: T1, rows: T2) -> Self
69    where
70        T1: IntoIterator,
71        T1::Item: Into<Unit>,
72        T2: IntoIterator,
73        T2::Item: Into<Unit>,
74    {
75        Self {
76            children: vec![],
77            rows: rows.into_iter().map(|r| r.into()).collect(),
78            cols: cols.into_iter().map(|c| c.into()).collect(),
79        }
80    }
81
82    /// Creates an new empty [`Grid`] with no rows or columns.
83    #[must_use]
84    pub fn empty() -> Self {
85        Self::default()
86    }
87
88    /// Adds a new row definition to the [`Grid`].
89    pub fn row(&mut self, row: Unit) {
90        self.rows.push(row);
91    }
92
93    /// Adds given column to current columns
94    pub fn col(&mut self, col: Unit) {
95        self.cols.push(col);
96    }
97
98    /// Adds child to the grid to given row and column
99    #[deprecated(
100        since = "0.6.0",
101        note = "Kept for compatibility purposes; use `push` function instead"
102    )]
103    pub fn add_child<T>(&mut self, child: T, col: usize, row: usize)
104    where
105        T: Into<Element>,
106    {
107        self.children.push(GridChild {
108            child: child.into(),
109            row,
110            col,
111        })
112    }
113
114    /// Adds a child widget at the specified column and row.
115    ///
116    /// # Parameters
117    /// - `child`: The widget to add (any type convertible to [`Element`])
118    /// - `col`: Zero-based column index (x)
119    /// - `row`: Zero-based row index (y)
120    pub fn push<T>(&mut self, child: T, col: usize, row: usize)
121    where
122        T: Into<Element>,
123    {
124        self.children.push(GridChild {
125            child: child.into(),
126            row,
127            col,
128        })
129    }
130}
131
132impl Widget for Grid {
133    fn render(&self, buffer: &mut Buffer, rect: Rect, cache: &mut Cache) {
134        if rect.is_empty() || self.children.is_empty() {
135            return;
136        }
137
138        let (cols, cols_pos, rows, rows_pos) = self.get_sizes(&rect, cache);
139
140        for (i, GridChild { child, row, col }) in
141            self.children.iter().enumerate()
142        {
143            let crect = Rect::new(
144                rect.x() + cols_pos[*col],
145                rect.y() + rows_pos[*row],
146                cols[*col],
147                rows[*row],
148            );
149            child.render(buffer, crect, &mut cache.children[i]);
150        }
151    }
152
153    fn height(&self, size: &Vec2) -> usize {
154        let mut height = 0;
155        for row in self.rows.iter() {
156            match row {
157                Unit::Length(len) => height += len,
158                Unit::Percent(p) => height += size.y * p / 100,
159                _ => {}
160            }
161        }
162        height
163    }
164
165    fn width(&self, size: &Vec2) -> usize {
166        let mut width = 0;
167        for col in self.cols.iter() {
168            match col {
169                Unit::Length(len) => width += len,
170                Unit::Percent(p) => width += size.y * p / 100,
171                _ => {}
172            }
173        }
174        width
175    }
176
177    fn children(&self) -> Vec<&Element> {
178        self.children.iter().map(|c| &c.child).collect()
179    }
180}
181
182impl Grid {
183    /// Gets sizes and starting positions of each row and column
184    fn get_sizes(
185        &self,
186        rect: &Rect,
187        cache: &mut Cache,
188    ) -> (Vec<usize>, Vec<usize>, Vec<usize>, Vec<usize>) {
189        match self.get_cache(rect, cache) {
190            Some((cols, rows)) => {
191                let cols_pos = Self::get_positions(&cols);
192                let rows_pos = Self::get_positions(&rows);
193                (cols, cols_pos, rows, rows_pos)
194            }
195            None => {
196                let cols = Self::get_size(&self.cols, rect.width());
197                let rows = Self::get_size(&self.rows, rect.height());
198                self.create_cache(rect, cache, &cols.0, &rows.0);
199                (cols.0, cols.1, rows.0, rows.1)
200            }
201        }
202    }
203
204    /// Gets sizes and positions of given units
205    fn get_size(units: &[Unit], size: usize) -> (Vec<usize>, Vec<usize>) {
206        let mut total = 0;
207        let mut fills_total = 0;
208
209        let mut sizes = Vec::new();
210        let mut positions = Vec::new();
211        let mut fills = Vec::new();
212        for unit in units {
213            let len = match unit {
214                Unit::Length(len) => *len,
215                Unit::Percent(p) => size * p / 100,
216                Unit::Fill(f) => {
217                    fills_total += f;
218                    fills.push(sizes.len());
219                    *f
220                }
221            };
222            sizes.push(len);
223            positions.push(total);
224            total += len;
225        }
226
227        if fills_total == 0 {
228            return (sizes, positions);
229        }
230
231        let mut pos = 0;
232        let remain = size.saturating_sub(total);
233        for (i, row) in units.iter().enumerate() {
234            match row {
235                Unit::Fill(f) => {
236                    sizes[i] = remain * f / fills_total;
237                    positions[i] = pos;
238                    pos += sizes[i];
239                }
240                _ => {
241                    positions[i] = pos;
242                    pos += sizes[i];
243                }
244            }
245        }
246
247        (sizes, positions)
248    }
249
250    fn get_positions(sizes: &[usize]) -> Vec<usize> {
251        let mut total = 0;
252        let mut pos = vec![];
253        for size in sizes {
254            pos.push(total);
255            total += size;
256        }
257        pos
258    }
259
260    fn get_cache(
261        &self,
262        rect: &Rect,
263        cache: &mut Cache,
264    ) -> Option<(Vec<usize>, Vec<usize>)> {
265        let lcache = cache.local::<GridCache>()?;
266        if !lcache.same_key(rect.size(), &self.cols, &self.rows) {
267            return None;
268        }
269        Some((lcache.col_sizes.clone(), lcache.row_sizes.clone()))
270    }
271
272    fn create_cache(
273        &self,
274        rect: &Rect,
275        cache: &mut Cache,
276        cols: &[usize],
277        rows: &[usize],
278    ) {
279        let lcache =
280            GridCache::new(*rect.size(), self.cols.clone(), self.rows.clone())
281                .sizes(cols.to_owned(), rows.to_owned());
282        cache.local = Some(Box::new(lcache));
283    }
284}
285
286impl From<Grid> for Box<dyn Widget> {
287    fn from(value: Grid) -> Self {
288        Box::new(value)
289    }
290}
291
292impl From<Grid> for Element {
293    fn from(value: Grid) -> Self {
294        Element::new(value)
295    }
296}