tui_additions/widgets/
grid.rs1use 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 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 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 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 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 {}