tui_additions/widgets/
grid.rs

1use std::{error::Error, fmt::Display};
2
3use ratatui::{
4    layout::{Constraint, Rect},
5    style::Style,
6    symbols::line::{self, Set, CROSS},
7    widgets::{BorderType, Widget},
8};
9
10#[derive(Clone)]
11pub struct Grid {
12    pub widths: Vec<Constraint>,
13    pub heights: Vec<Constraint>,
14    pub border_type: BorderType,
15    pub border_style: Style,
16}
17
18impl Grid {
19    pub fn new(widths: Vec<Constraint>, heights: Vec<Constraint>) -> Result<Self, GridError> {
20        if widths.is_empty() || heights.is_empty() {
21            return Err(GridError::ZeroLength);
22        }
23        Ok(Self {
24            widths,
25            heights,
26            border_type: BorderType::Plain,
27            border_style: Style::default(),
28        })
29    }
30}
31
32impl Grid {
33    pub fn chunks(&self, area: Rect) -> Result<Vec<Vec<Rect>>, GridError> {
34        let widths = self.widths(area.width)?;
35        let heights = self.heights(area.height)?;
36
37        let xs = {
38            let mut xs = Self::lines(area.x, &widths);
39            xs.truncate(self.widths.len());
40            xs.iter_mut().for_each(|item| *item += 1);
41            xs
42        };
43        let ys = {
44            let mut ys = Self::lines(area.y, &heights);
45            ys.truncate(self.heights.len());
46            ys.iter_mut().for_each(|item| *item += 1);
47            ys
48        };
49
50        Ok(ys
51            .iter()
52            .zip(heights.iter())
53            .map(|(y, height)| {
54                let row = xs
55                    .iter()
56                    .zip(widths.iter())
57                    .map(|(x, width)| Rect::new(*x, *y, *width, *height - 1))
58                    .collect::<Vec<_>>();
59                row
60            })
61            .collect::<Vec<_>>())
62    }
63
64    pub fn lines(mut position: u16, lengths: &[u16]) -> Vec<u16> {
65        let mut lines = Vec::new();
66        lengths.iter().for_each(|lengths| {
67            lines.push(position);
68            position += 1 + *lengths;
69        });
70        // position -= 1;
71        lines.push(position);
72
73        lines
74    }
75
76    pub fn heights(&self, height: u16) -> Result<Vec<u16>, GridError> {
77        Self::lengths(&self.heights, height)
78    }
79
80    pub fn widths(&self, width: u16) -> Result<Vec<u16>, GridError> {
81        Self::lengths(&self.widths, width - 1)
82    }
83
84    pub fn lengths(constraints: &[Constraint], mut length: u16) -> Result<Vec<u16>, GridError> {
85        if length < constraints.len() as u16 + 1 {
86            return Err(GridError::NotEnoughLength);
87        }
88
89        length -= constraints.len() as u16;
90
91        let mut lengths = constraints
92            .iter()
93            .map(|constraint| constraint.apply(length))
94            .collect::<Vec<_>>();
95        let sum: u16 = lengths.iter().sum();
96
97        if sum < length {
98            *lengths.last_mut().unwrap() += length - sum;
99        }
100        // .collect::<Vec<_>>();
101
102        Ok(lengths)
103    }
104}
105
106impl Grid {
107    pub fn from_pos<'a>(
108        x: &'a u16,
109        y: &'a u16,
110        left: &'a u16,
111        right: &'a u16,
112        top: &'a u16,
113        bottom: &'a u16,
114        set: &'a Set,
115    ) -> &'a str {
116        let is_top = y == top;
117        let is_bottom = y == bottom;
118        let is_left = x == left;
119        let is_right = x == right;
120
121        if is_top {
122            if is_left {
123                return set.top_left;
124            }
125
126            if is_right {
127                return set.top_right;
128            }
129
130            return set.horizontal_down;
131        }
132
133        if is_bottom {
134            if is_left {
135                return set.bottom_left;
136            }
137
138            if is_right {
139                return set.bottom_right;
140            }
141
142            return set.horizontal_up;
143        }
144
145        if is_left {
146            return set.vertical_right;
147        }
148
149        if is_right {
150            return set.vertical_left;
151        }
152
153        CROSS
154    }
155}
156
157impl Grid {
158    pub fn border_type(mut self, border_type: BorderType) -> Self {
159        self.set_border_type(border_type);
160        self
161    }
162
163    pub fn set_border_type(&mut self, border_type: BorderType) {
164        self.border_type = border_type;
165    }
166
167    pub fn border_style(mut self, border_style: Style) -> Self {
168        self.set_border_style(border_style);
169        self
170    }
171
172    pub fn set_border_style(&mut self, border_style: Style) {
173        self.border_style = border_style;
174    }
175}
176
177impl Widget for Grid {
178    fn render(self, mut area: Rect, buf: &mut ratatui::buffer::Buffer) {
179        area.height -= 1;
180
181        let widths = self.widths(area.width).unwrap();
182        let heights = self.heights(area.height).unwrap();
183        let vertical_lines = Self::lines(area.x, &widths);
184        let horizontal_lines = Self::lines(area.y, &heights);
185
186        let top = horizontal_lines.first().unwrap();
187        let bottom = horizontal_lines.last().unwrap();
188        let left = vertical_lines.first().unwrap();
189        let right = vertical_lines.last().unwrap();
190
191        let set = match self.border_type {
192            BorderType::Plain => line::NORMAL,
193            BorderType::Thick => line::THICK,
194            BorderType::Double => line::DOUBLE,
195            BorderType::Rounded => line::ROUNDED,
196            _ => panic!("no such line type"),
197        };
198
199        // vertical lines
200        for x in vertical_lines.iter() {
201            for y in *top..*bottom + 1 {
202                if !horizontal_lines.contains(&y) {
203                    buf.set_string(*x, y, set.vertical, self.border_style);
204                }
205            }
206        }
207
208        // horizontal lines
209        for y in horizontal_lines.iter() {
210            for x in *left..*right + 1 {
211                if vertical_lines.contains(&x) {
212                    buf.set_string(
213                        x,
214                        *y,
215                        Self::from_pos(&x, y, left, right, top, bottom, &set),
216                        self.border_style,
217                    );
218                } else {
219                    buf.set_string(x, *y, set.horizontal, self.border_style);
220                }
221            }
222        }
223    }
224}
225
226#[derive(Debug, PartialEq, Eq)]
227pub enum GridError {
228    NotEnoughLength,
229    ZeroLength,
230}
231
232impl Display for GridError {
233    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
234        f.write_fmt(format_args!("{:?}", self))
235    }
236}
237
238impl Error for GridError {}