rat_ftable/
textdata.rs

1//!
2//! Implements a Row and a Cell struct that are compatible to ratatui.
3//! You only need these if you use preformatted data.
4//!
5
6use crate::_private::NonExhaustive;
7use crate::{TableContext, TableData};
8use ratatui::buffer::Buffer;
9use ratatui::layout::Rect;
10use ratatui::style::{Style, Styled};
11use ratatui::text::Text;
12use ratatui::widgets::Widget;
13
14/// Internal impl for TableData using pre-rendered Cells.
15#[derive(Debug, Default, Clone)]
16pub(crate) struct TextTableData<'a> {
17    pub(crate) rows: Vec<Row<'a>>,
18}
19
20/// Rows of the table.
21#[derive(Debug, Clone)]
22pub struct Row<'a> {
23    pub cells: Vec<Cell<'a>>,
24    pub top_margin: u16,
25    pub height: u16,
26    pub bottom_margin: u16,
27    pub style: Option<Style>,
28
29    pub non_exhaustive: NonExhaustive,
30}
31
32/// A single cell of the table.
33#[derive(Debug, Clone)]
34pub struct Cell<'a> {
35    pub content: Text<'a>,
36    pub style: Option<Style>,
37
38    pub non_exhaustive: NonExhaustive,
39}
40
41impl<'a> TableData<'a> for TextTableData<'a> {
42    fn rows(&self) -> usize {
43        self.rows.len()
44    }
45
46    fn row_height(&self, r: usize) -> u16 {
47        if let Some(row) = self.rows.get(r) {
48            row.top_margin + row.height + row.bottom_margin
49        } else {
50            0
51        }
52    }
53
54    fn row_style(&self, r: usize) -> Option<Style> {
55        if let Some(row) = self.rows.get(r) {
56            row.style
57        } else {
58            None
59        }
60    }
61
62    #[allow(clippy::collapsible_if)]
63    fn render_cell(&self, _ctx: &TableContext, c: usize, r: usize, area: Rect, buf: &mut Buffer) {
64        if let Some(row) = self.rows.get(r) {
65            if let Some(cell) = row.cell(c) {
66                if let Some(style) = cell.style {
67                    buf.set_style(area, style);
68                }
69                cell.content.clone().render(area, buf);
70            }
71        }
72    }
73}
74
75impl Default for Row<'_> {
76    fn default() -> Self {
77        Self {
78            cells: Default::default(),
79            top_margin: 0,
80            height: 0,
81            bottom_margin: 0,
82            style: Default::default(),
83            non_exhaustive: NonExhaustive,
84        }
85    }
86}
87
88impl Styled for Row<'_> {
89    type Item = Self;
90
91    fn style(&self) -> Style {
92        self.style.unwrap_or_default()
93    }
94
95    fn set_style<S: Into<Style>>(mut self, style: S) -> Self::Item {
96        self.style = Some(style.into());
97        self
98    }
99}
100
101impl<'a, Item> FromIterator<Item> for Row<'a>
102where
103    Item: Into<Cell<'a>>,
104{
105    fn from_iter<T: IntoIterator<Item = Item>>(cells: T) -> Self {
106        Self::new(cells)
107    }
108}
109
110impl<'a> Row<'a> {
111    /// New row of data cells.
112    pub fn new<T>(cells: T) -> Self
113    where
114        T: IntoIterator,
115        T::Item: Into<Cell<'a>>,
116    {
117        let mut s = Self {
118            cells: cells.into_iter().map(|v| v.into()).collect(),
119            height: 1,
120            ..Default::default()
121        };
122        // content heigth
123        if let Some(height) = s.cells.iter().map(|v| v.content.height()).max() {
124            s.height = height as u16;
125        }
126        s
127    }
128
129    /// Set the data cells for the row.
130    pub fn cells<T>(mut self, cells: T) -> Self
131    where
132        T: IntoIterator,
133        T::Item: Into<Cell<'a>>,
134    {
135        self.cells = cells.into_iter().map(Into::into).collect();
136        self
137    }
138
139    /// Set the row-height.
140    #[inline]
141    pub fn height(mut self, height: u16) -> Self {
142        self.height = height;
143        self
144    }
145
146    /// Add some margin.
147    pub fn top_margin(mut self, margin: u16) -> Self {
148        self.top_margin = margin;
149        self
150    }
151
152    /// Add some margin.
153    pub fn bottom_margin(mut self, margin: u16) -> Self {
154        self.bottom_margin = margin;
155        self
156    }
157
158    /// Rowstyle.
159    pub fn style(mut self, style: Option<Style>) -> Self {
160        self.style = style;
161        self
162    }
163
164    /// Access to the cell.
165    pub fn cell<'b: 'a>(&'b self, c: usize) -> Option<&'a Cell<'a>> {
166        if let Some(t) = self.cells.get(c) {
167            Some(t)
168        } else {
169            None
170        }
171    }
172}
173
174impl Default for Cell<'_> {
175    fn default() -> Self {
176        Self {
177            content: Default::default(),
178            style: Default::default(),
179            non_exhaustive: NonExhaustive,
180        }
181    }
182}
183
184impl<'a, T> From<T> for Cell<'a>
185where
186    T: Into<Text<'a>>,
187{
188    fn from(value: T) -> Self {
189        Self {
190            content: value.into(),
191            style: Default::default(),
192            non_exhaustive: NonExhaustive,
193        }
194    }
195}
196
197impl Styled for Cell<'_> {
198    type Item = Self;
199
200    fn style(&self) -> Style {
201        self.style.unwrap_or_default()
202    }
203
204    fn set_style<S: Into<Style>>(mut self, style: S) -> Self {
205        self.style = Some(style.into());
206        self
207    }
208}
209
210impl<'a> Cell<'a> {
211    /// New Cell.
212    pub fn new<T>(content: T) -> Self
213    where
214        T: Into<Text<'a>>,
215    {
216        Self {
217            content: content.into(),
218            style: Default::default(),
219            non_exhaustive: NonExhaustive,
220        }
221    }
222
223    /// Set the cell content.
224    pub fn content<T>(mut self, content: T) -> Self
225    where
226        T: Into<Text<'a>>,
227    {
228        self.content = content.into();
229        self
230    }
231
232    /// Cell style.
233    pub fn style(mut self, style: Option<Style>) -> Self {
234        self.style = style;
235        self
236    }
237}