1use std::{
2 fmt::Display,
3 io::{Result, Write},
4};
5
6use termcolor::{Buffer, BufferWriter, Color, ColorSpec, WriteColor};
7
8use crate::{
9 row::Dimension as RowDimension,
10 style::{Style, StyleStruct},
11 utils::display_width,
12};
13
14pub struct CellStruct {
16 data: Vec<String>,
17 format: CellFormat,
18 style: StyleStruct,
19}
20
21impl CellStruct {
22 pub fn justify(mut self, justify: Justify) -> CellStruct {
24 self.format.justify = justify;
25 self
26 }
27
28 pub fn align(mut self, align: Align) -> CellStruct {
30 self.format.align = align;
31 self
32 }
33
34 pub fn padding(mut self, padding: Padding) -> CellStruct {
36 self.format.padding = padding;
37 self
38 }
39
40 fn color_spec(&self) -> ColorSpec {
41 self.style.color_spec()
42 }
43
44 pub(crate) fn required_dimension(&self) -> Dimension {
46 let height = self.data.len() + self.format.padding.top + self.format.padding.bottom;
47 let width = self
48 .data
49 .iter()
50 .map(|x| display_width(x))
51 .max()
52 .unwrap_or_default()
53 + self.format.padding.left
54 + self.format.padding.right;
55
56 Dimension { width, height }
57 }
58
59 pub(crate) fn buffers(
60 &self,
61 writer: &BufferWriter,
62 available_dimension: Dimension,
63 ) -> Result<Vec<Buffer>> {
64 let required_dimension = self.required_dimension();
65 let mut buffers = Vec::with_capacity(available_dimension.height);
66
67 assert!(
68 available_dimension >= required_dimension,
69 "Available dimensions for a cell are smaller than required. Please create an issue in https://github.com/devashishdxt/cli-table"
70 );
71
72 let top_blank_lines = self.top_blank_lines(available_dimension, required_dimension);
73
74 for _ in 0..top_blank_lines {
75 buffers.push(self.buffer(writer, available_dimension, required_dimension, "")?);
76 }
77
78 for line in self.data.clone().iter() {
79 buffers.push(self.buffer(writer, available_dimension, required_dimension, line)?);
80 }
81
82 for _ in 0..(available_dimension.height - (self.data.len() + top_blank_lines)) {
83 buffers.push(self.buffer(writer, available_dimension, required_dimension, "")?);
84 }
85
86 Ok(buffers)
87 }
88
89 fn buffer(
90 &self,
91 writer: &BufferWriter,
92 available_dimension: Dimension,
93 required_dimension: Dimension,
94 data: &str,
95 ) -> Result<Buffer> {
96 let empty_chars = match self.format.justify {
97 Justify::Left => self.format.padding.left,
98 Justify::Right => {
99 (available_dimension.width - required_dimension.width) + self.format.padding.left
100 }
101 Justify::Center => {
102 ((available_dimension.width - required_dimension.width) / 2)
103 + self.format.padding.left
104 }
105 };
106
107 let mut buffer = writer.buffer();
108 buffer.set_color(&self.color_spec())?;
109
110 for _ in 0..empty_chars {
111 write!(buffer, " ")?;
112 }
113
114 write!(buffer, "{}", data)?;
115
116 for _ in 0..(available_dimension.width - (display_width(data) + empty_chars)) {
117 write!(buffer, " ")?;
118 }
119
120 Ok(buffer)
121 }
122
123 fn top_blank_lines(
124 &self,
125 available_dimension: Dimension,
126 required_dimension: Dimension,
127 ) -> usize {
128 match self.format.align {
129 Align::Top => self.format.padding.top,
130 Align::Bottom => {
131 (available_dimension.height - required_dimension.height) + self.format.padding.top
132 }
133 Align::Center => {
134 ((available_dimension.height - required_dimension.height) / 2)
135 + self.format.padding.top
136 }
137 }
138 }
139}
140
141pub trait Cell {
143 fn cell(self) -> CellStruct;
145}
146
147impl<T> Cell for T
148where
149 T: Display,
150{
151 fn cell(self) -> CellStruct {
152 let data = self.to_string().lines().map(ToString::to_string).collect();
153
154 CellStruct {
155 data,
156 format: Default::default(),
157 style: Default::default(),
158 }
159 }
160}
161
162impl Cell for CellStruct {
163 fn cell(self) -> CellStruct {
164 self
165 }
166}
167
168impl Style for CellStruct {
169 fn foreground_color(mut self, foreground_color: Option<Color>) -> Self {
170 self.style = self.style.foreground_color(foreground_color);
171 self
172 }
173
174 fn background_color(mut self, background_color: Option<Color>) -> Self {
175 self.style = self.style.background_color(background_color);
176 self
177 }
178
179 fn bold(mut self, bold: bool) -> Self {
180 self.style = self.style.bold(bold);
181 self
182 }
183
184 fn underline(mut self, underline: bool) -> Self {
185 self.style = self.style.underline(underline);
186 self
187 }
188
189 fn italic(mut self, italic: bool) -> Self {
190 self.style = self.style.italic(italic);
191 self
192 }
193
194 fn intense(mut self, intense: bool) -> Self {
195 self.style = self.style.intense(intense);
196 self
197 }
198
199 fn dimmed(mut self, dimmed: bool) -> Self {
200 self.style = self.style.dimmed(dimmed);
201 self
202 }
203}
204
205#[derive(Debug, Clone, Copy, Default)]
207struct CellFormat {
208 justify: Justify,
209 align: Align,
210 padding: Padding,
211}
212
213#[derive(Debug, Clone, Copy)]
215pub enum Justify {
216 Left,
218 Right,
220 Center,
222}
223
224impl Default for Justify {
225 fn default() -> Self {
226 Self::Left
227 }
228}
229
230#[derive(Debug, Clone, Copy)]
232pub enum Align {
233 Top,
235 Bottom,
237 Center,
239}
240
241impl Default for Align {
242 fn default() -> Self {
243 Self::Top
244 }
245}
246
247#[derive(Debug, Clone, Copy, Default)]
249pub struct Padding {
250 pub(crate) left: usize,
252 pub(crate) right: usize,
254 pub(crate) top: usize,
256 pub(crate) bottom: usize,
258}
259
260impl Padding {
261 pub fn builder() -> PaddingBuilder {
263 Default::default()
264 }
265}
266
267#[derive(Debug, Default)]
269pub struct PaddingBuilder(Padding);
270
271impl PaddingBuilder {
272 pub fn left(mut self, left_padding: usize) -> Self {
274 self.0.left = left_padding;
275 self
276 }
277
278 pub fn right(mut self, right_padding: usize) -> Self {
280 self.0.right = right_padding;
281 self
282 }
283
284 pub fn top(mut self, top_padding: usize) -> Self {
286 self.0.top = top_padding;
287 self
288 }
289
290 pub fn bottom(mut self, bottom_padding: usize) -> Self {
292 self.0.bottom = bottom_padding;
293 self
294 }
295
296 pub fn build(self) -> Padding {
298 self.0
299 }
300}
301
302#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
304pub(crate) struct Dimension {
305 pub(crate) width: usize,
307 pub(crate) height: usize,
309}
310
311impl From<RowDimension> for Vec<Dimension> {
312 fn from(row_dimension: RowDimension) -> Self {
313 let height = row_dimension.height;
314
315 row_dimension
316 .widths
317 .into_iter()
318 .map(|width| Dimension { width, height })
319 .collect()
320 }
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326
327 #[test]
328 fn test_into_cell() {
329 let cell = "Hello".cell();
330 assert_eq!(1, cell.data.len());
331 assert_eq!("Hello", cell.data[0]);
332 }
333}