Skip to main content

tui_banner/
grid.rs

1// Copyright (c) 2025 Lei Zhang
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
13use crate::color::Color;
14
15/// Single cell in the grid.
16#[derive(Clone, Debug)]
17pub struct Cell {
18    /// Character rendered at this cell.
19    pub ch: char,
20    /// Foreground color.
21    pub fg: Option<Color>,
22    /// Background color.
23    pub bg: Option<Color>,
24    /// Visibility flag (used for effects).
25    pub visible: bool,
26}
27
28/// 2D grid of cells.
29#[derive(Clone, Debug)]
30pub struct Grid {
31    cells: Vec<Vec<Cell>>,
32}
33
34/// Horizontal alignment.
35#[derive(Clone, Copy, Debug)]
36pub enum Align {
37    /// Align to the left.
38    Left,
39    /// Center align.
40    Center,
41    /// Align to the right.
42    Right,
43}
44
45/// Padding around a grid.
46#[derive(Clone, Copy, Debug)]
47pub struct Padding {
48    /// Top padding.
49    pub top: usize,
50    /// Bottom padding.
51    pub bottom: usize,
52    /// Left padding.
53    pub left: usize,
54    /// Right padding.
55    pub right: usize,
56}
57
58impl Grid {
59    /// Create an empty grid with given dimensions.
60    pub fn new(height: usize, width: usize) -> Self {
61        let row = vec![
62            Cell {
63                ch: ' ',
64                fg: None,
65                bg: None,
66                visible: false,
67            };
68            width
69        ];
70        let cells = vec![row; height];
71        Self { cells }
72    }
73
74    /// Build a grid from raw character rows.
75    pub fn from_char_rows(rows: Vec<Vec<char>>) -> Self {
76        let cells = rows
77            .into_iter()
78            .map(|row| {
79                row.into_iter()
80                    .map(|ch| Cell {
81                        ch,
82                        fg: None,
83                        bg: None,
84                        visible: ch != ' ',
85                    })
86                    .collect::<Vec<_>>()
87            })
88            .collect::<Vec<_>>();
89        Self { cells }
90    }
91
92    /// Height of the grid.
93    pub fn height(&self) -> usize {
94        self.cells.len()
95    }
96
97    /// Width of the grid.
98    pub fn width(&self) -> usize {
99        self.cells.first().map(|row| row.len()).unwrap_or(0)
100    }
101
102    /// Mutable cell access.
103    pub fn cell_mut(&mut self, row: usize, col: usize) -> Option<&mut Cell> {
104        self.cells.get_mut(row).and_then(|r| r.get_mut(col))
105    }
106
107    /// Immutable cell access.
108    pub fn cell(&self, row: usize, col: usize) -> Option<&Cell> {
109        self.cells.get(row).and_then(|r| r.get(col))
110    }
111
112    /// Borrow rows.
113    pub fn rows(&self) -> &[Vec<Cell>] {
114        &self.cells
115    }
116
117    /// Borrow rows mutably.
118    pub fn rows_mut(&mut self) -> &mut [Vec<Cell>] {
119        &mut self.cells
120    }
121
122    /// Blit another grid onto this grid at the given offset.
123    pub fn blit(&mut self, other: &Grid, top: usize, left: usize) {
124        for (r, row) in other.cells.iter().enumerate() {
125            let target_r = top + r;
126            if target_r >= self.height() {
127                continue;
128            }
129            for (c, cell) in row.iter().enumerate() {
130                let target_c = left + c;
131                if target_c >= self.width() {
132                    continue;
133                }
134                if cell.visible {
135                    self.cells[target_r][target_c] = cell.clone();
136                }
137            }
138        }
139    }
140
141    /// Trim fully blank rows from the top and bottom.
142    pub fn trim_vertical(&self) -> Self {
143        if self.height() == 0 {
144            return self.clone();
145        }
146
147        let mut top = 0;
148        let mut bottom = self.height();
149
150        while top < bottom && !row_has_visible(&self.cells[top]) {
151            top += 1;
152        }
153
154        while bottom > top && !row_has_visible(&self.cells[bottom - 1]) {
155            bottom -= 1;
156        }
157
158        if top == 0 && bottom == self.height() {
159            return self.clone();
160        }
161
162        Grid {
163            cells: self.cells[top..bottom].to_vec(),
164        }
165    }
166}
167
168fn row_has_visible(row: &[Cell]) -> bool {
169    row.iter().any(|cell| cell.visible)
170}
171
172impl Padding {
173    /// Uniform padding on all sides.
174    pub fn uniform(value: usize) -> Self {
175        Self {
176            top: value,
177            bottom: value,
178            left: value,
179            right: value,
180        }
181    }
182}
183
184impl From<usize> for Padding {
185    fn from(value: usize) -> Self {
186        Padding::uniform(value)
187    }
188}
189
190impl From<(usize, usize, usize, usize)> for Padding {
191    fn from(values: (usize, usize, usize, usize)) -> Self {
192        Self {
193            top: values.0,
194            right: values.1,
195            bottom: values.2,
196            left: values.3,
197        }
198    }
199}