tabled/features/
panel.rs

1//! This module contains primitivies to create a spread row.
2//! Ultimately it is a cell with a span set to a number of columns on the [`Table`].
3//!
4//! You can use a [`Span`] to set a custom span.
5//!
6//! # Example
7//!
8//! ```
9//! use tabled::{TableIteratorExt, Panel};
10//!
11//! let data = [[1, 2, 3], [4, 5, 6]];
12//!
13//! let table = data.table()
14//!     .with(Panel::vertical(1).text("Split").text_width(1))
15//!     .with(Panel::header("Numbers"))
16//!     .to_string();
17//!
18//! println!("{}", table);
19//!
20//! assert_eq!(
21//!     table,
22//!     concat!(
23//!         "+---+---+---+---+\n",
24//!         "| Numbers       |\n",
25//!         "+---+---+---+---+\n",
26//!         "| 0 | S | 1 | 2 |\n",
27//!         "+---+ p +---+---+\n",
28//!         "| 1 | l | 2 | 3 |\n",
29//!         "+---+ i +---+---+\n",
30//!         "| 4 | t | 5 | 6 |\n",
31//!         "+---+---+---+---+",
32//!     )
33//! )
34//! ```
35//!
36//! [`Table`]: crate::Table
37//! [`Span`]: crate::Span
38
39use papergrid::{
40    records::{Records, RecordsMut, Resizable},
41    width::CfgWidthFunction,
42    Position,
43};
44
45use crate::{width::wrap_text, Table, TableOption};
46
47/// Panel allows to add a Row which has 1 continues Cell to a [`Table`].
48///
49/// See `examples/panel.rs`.
50///
51/// [`Table`]: crate::Table
52#[derive(Debug)]
53pub struct Panel;
54
55impl Panel {
56    /// Creates an empty vertical row at given index.
57    ///
58    /// ```
59    /// use tabled::{Panel, TableIteratorExt};
60    ///
61    /// let data = [[1, 2, 3], [4, 5, 6]];
62    ///
63    /// let table = data.table()
64    ///     .with(Panel::vertical(1).text("Tabled Releases"))
65    ///     .to_string();
66    ///
67    /// println!("{}", table);
68    ///
69    /// assert_eq!(
70    ///     table,
71    ///     concat!(
72    ///         "+---+-----------------+---+---+\n",
73    ///         "| 0 | Tabled Releases | 1 | 2 |\n",
74    ///         "+---+                 +---+---+\n",
75    ///         "| 1 |                 | 2 | 3 |\n",
76    ///         "+---+                 +---+---+\n",
77    ///         "| 4 |                 | 5 | 6 |\n",
78    ///         "+---+-----------------+---+---+",
79    ///     )
80    /// )
81    /// ```
82    pub fn vertical(column: usize) -> VerticalPanel<&'static str> {
83        VerticalPanel {
84            pos: (0, column),
85            text: "",
86            text_width: 0,
87        }
88    }
89
90    /// Creates an empty horizontal row at given index.
91    ///
92    /// ```
93    /// use tabled::{Panel, TableIteratorExt};
94    ///
95    /// let data = [[1, 2, 3], [4, 5, 6]];
96    ///
97    /// let table = data.table()
98    ///     .with(Panel::vertical(1))
99    ///     .to_string();
100    ///
101    /// println!("{}", table);
102    ///
103    /// assert_eq!(
104    ///     table,
105    ///     concat!(
106    ///         "+---+--+---+---+\n",
107    ///         "| 0 |  | 1 | 2 |\n",
108    ///         "+---+  +---+---+\n",
109    ///         "| 1 |  | 2 | 3 |\n",
110    ///         "+---+  +---+---+\n",
111    ///         "| 4 |  | 5 | 6 |\n",
112    ///         "+---+--+---+---+",
113    ///     )
114    /// )
115    /// ```
116    pub fn horizontal(row: usize) -> HorizontalPanel<&'static str> {
117        HorizontalPanel {
118            text: "",
119            pos: (row, 0),
120        }
121    }
122
123    /// Creates an horizontal row at first row.
124    pub fn header<S>(text: S) -> Header<S> {
125        Header(text)
126    }
127
128    /// Creates an horizontal row at last row.
129    pub fn footer<S>(text: S) -> Footer<S> {
130        Footer(text)
131    }
132}
133
134/// A vertical/row span from 0 to a count columns.
135#[derive(Debug)]
136pub struct VerticalPanel<S> {
137    text: S,
138    pos: Position,
139    text_width: usize,
140}
141
142impl<S> VerticalPanel<S> {
143    /// Set a text for a panel.
144    pub fn text<T>(self, text: T) -> VerticalPanel<T>
145    where
146        T: AsRef<str> + Clone,
147    {
148        VerticalPanel {
149            pos: self.pos,
150            text_width: self.text_width,
151            text,
152        }
153    }
154
155    /// Changes the default row=0 to a custom row.
156    /// So the panel will be limited from row to count rows.
157    pub fn row(mut self, row: usize) -> Self {
158        self.pos.0 = row;
159        self
160    }
161
162    /// Define a maximum width of text.
163    pub fn text_width(mut self, width: usize) -> Self {
164        self.text_width = width;
165        self
166    }
167
168    fn get_text(&self) -> String
169    where
170        S: AsRef<str>,
171    {
172        let text = if self.text_width > 0 {
173            wrap_text(self.text.as_ref(), self.text_width, false)
174        } else {
175            self.text.as_ref().to_owned()
176        };
177        text
178    }
179}
180
181impl<S, R> TableOption<R> for VerticalPanel<S>
182where
183    S: AsRef<str> + Clone,
184    R: Records + RecordsMut<String> + Resizable,
185{
186    fn change(&mut self, table: &mut Table<R>) {
187        let (count_rows, count_cols) = table.shape();
188        if self.pos.1 > count_cols || self.pos.0 > count_rows {
189            return;
190        }
191
192        move_columns_aside(table, self.pos.1);
193        move_column_spans(table, self.pos.1);
194        // move_right_borders(table, self.pos.0, self.pos.1);
195        // #[cfg(feature = "color")]
196        // move_right_border_colors(table, self.pos.0, self.pos.1);
197
198        let text = self.get_text();
199        set_text(table, self.pos, text);
200
201        let length = count_rows.checked_sub(self.pos.0).unwrap_or(1);
202        table.get_config_mut().set_row_span(self.pos, length);
203
204        table.destroy_width_cache();
205        table.destroy_height_cache();
206    }
207}
208
209/// A horizontal/column span from 0 to a count rows.
210#[derive(Debug)]
211pub struct HorizontalPanel<S> {
212    text: S,
213    pos: Position,
214}
215
216impl<S> HorizontalPanel<S> {
217    /// Sets a text value.
218    pub fn text<T>(self, text: T) -> HorizontalPanel<T>
219    where
220        T: AsRef<str> + Clone,
221    {
222        HorizontalPanel {
223            pos: self.pos,
224            text,
225        }
226    }
227
228    pub fn column(mut self, column: usize) -> Self {
229        self.pos.1 = column;
230        self
231    }
232}
233
234impl<S, R> TableOption<R> for HorizontalPanel<S>
235where
236    S: AsRef<str> + Clone,
237    R: Records + RecordsMut<String> + Resizable,
238{
239    fn change(&mut self, table: &mut Table<R>) {
240        let (count_rows, count_cols) = table.shape();
241        if self.pos.0 > count_rows {
242            return;
243        }
244
245        move_rows_aside(table, self.pos.0);
246        move_row_spans(table, self.pos.0);
247        // move_lines_aside(table, self.pos.0);
248        // move_text_on_lines_aside(table, self.pos.0);
249        // move_aside_borders(table, self.pos.0);
250        // #[cfg(feature = "color")]
251        // move_aside_border_colors(table, self.pos.0);
252
253        set_text(table, self.pos, self.text.as_ref().to_owned());
254
255        let length = count_cols.checked_sub(self.pos.1).unwrap_or(1);
256        table.get_config_mut().set_column_span(self.pos, length);
257
258        table.destroy_width_cache();
259        table.destroy_height_cache();
260    }
261}
262
263/// Header inserts a [`Panel`] at the top.
264/// See [`Panel`].
265#[derive(Debug)]
266pub struct Header<S>(S);
267
268impl<S, R> TableOption<R> for Header<S>
269where
270    S: AsRef<str>,
271    R: Records + RecordsMut<String> + Resizable,
272{
273    fn change(&mut self, table: &mut Table<R>) {
274        HorizontalPanel {
275            pos: (0, 0),
276            text: self.0.as_ref(),
277        }
278        .change(table);
279    }
280}
281
282/// Footer renders a [`Panel`] at the bottom.
283/// See [`Panel`].
284#[derive(Debug)]
285pub struct Footer<S>(S);
286
287impl<S, R> TableOption<R> for Footer<S>
288where
289    S: AsRef<str> + Clone,
290    R: Records + RecordsMut<String> + Resizable,
291{
292    fn change(&mut self, table: &mut Table<R>) {
293        HorizontalPanel {
294            pos: (table.shape().0, 0),
295            text: self.0.as_ref(),
296        }
297        .change(table);
298    }
299}
300
301fn move_rows_aside<R>(table: &mut Table<R>, row: usize)
302where
303    R: Records + Resizable,
304{
305    table.get_records_mut().push_row();
306
307    let count_rows = table.get_records().count_rows();
308    let shift_count = count_rows - row;
309    for i in 0..shift_count {
310        let row = count_rows - i;
311        table.get_records_mut().swap_row(row, row - 1);
312    }
313}
314
315fn move_columns_aside<R>(table: &mut Table<R>, column: usize)
316where
317    R: Records + Resizable,
318{
319    table.get_records_mut().push_column();
320
321    let count_columns = table.get_records().count_columns();
322    let shift_count = count_columns - column;
323    for i in 0..shift_count {
324        let col = count_columns - i;
325        table.get_records_mut().swap_column(col, col - 1);
326    }
327}
328
329fn move_row_spans<R>(table: &mut Table<R>, target_row: usize)
330where
331    R: Records,
332{
333    let spans = table
334        .get_config()
335        .iter_column_spans(table.shape())
336        .collect::<Vec<_>>();
337    for ((row, col), span) in spans {
338        if row >= target_row {
339            table.get_config_mut().set_column_span((row, col), 1);
340            table.get_config_mut().set_column_span((row + 1, col), span);
341        }
342    }
343
344    let spans = table
345        .get_config()
346        .iter_row_spans(table.shape())
347        .collect::<Vec<_>>();
348    for ((row, col), span) in spans {
349        if row >= target_row {
350            table.get_config_mut().set_row_span((row, col), 1);
351            table.get_config_mut().set_row_span((row + 1, col), span);
352        } else {
353            // let span_covers_row = target_row <= row + span;
354            // if span_covers_row {
355            //     table.get_config_mut().set_row_span((row, col), span + 1);
356            // }
357        }
358    }
359}
360
361fn move_column_spans<R>(table: &mut Table<R>, target_column: usize)
362where
363    R: Records,
364{
365    let spans = table
366        .get_config()
367        .iter_column_spans(table.shape())
368        .collect::<Vec<_>>();
369    for ((row, col), span) in spans {
370        if col >= target_column {
371            table.get_config_mut().set_column_span((row, col), 1);
372            table.get_config_mut().set_column_span((row, col + 1), span);
373        } else {
374            // let span_covers_column = target_column <= col + span;
375            // if span_covers_column {
376            //     table.get_config_mut().set_column_span((row, col), span + 1);
377            // }
378        }
379    }
380
381    let spans = table
382        .get_config()
383        .iter_row_spans(table.shape())
384        .collect::<Vec<_>>();
385    for ((row, col), span) in spans {
386        if col >= target_column {
387            table.get_config_mut().set_row_span((row, col), 1);
388            table.get_config_mut().set_row_span((row, col + 1), span);
389        }
390    }
391}
392
393fn set_text<R>(table: &mut Table<R>, pos: Position, text: String)
394where
395    R: RecordsMut<String>,
396{
397    let ctrl = CfgWidthFunction::from_cfg(table.get_config());
398    table.get_records_mut().set(pos, text, ctrl);
399}
400
401// fn move_lines_aside<R>(table: &mut Table<R>, row: usize)
402// where
403//     R: Records,
404// {
405//     let count_rows = table.get_records().count_rows();
406//     if count_rows < 2 {
407//         return;
408//     }
409
410//     for i in (row..count_rows - 1).rev() {
411//         if let Some(line) = table.get_config().get_split_line(i).cloned() {
412//             table.get_config_mut().remove_split_line(i);
413//             table.get_config_mut().set_split_line(i + 1, line);
414//         }
415//     }
416// }
417
418// fn move_text_on_lines_aside<R>(table: &mut Table<R>, row: usize)
419// where
420//     R: Records,
421// {
422//     let count_rows = table.get_records().count_rows();
423//     if count_rows < 2 {
424//         return;
425//     }
426
427//     for i in (row..count_rows - 1).rev() {
428//         if let Some(line) = table.get_config_mut().remove_split_line_text(i) {
429//             table.get_config_mut().override_split_line(i + 1, line);
430//         }
431//     }
432// }
433
434// fn move_aside_borders<R>(table: &mut Table<R>, row: usize)
435// where
436//     R: Records,
437// {
438//     let count_rows = table.get_records().count_rows();
439//     if count_rows < 2 {
440//         return;
441//     }
442
443//     let count_columns = table.get_records().count_columns();
444
445//     for i in (row + 1..count_rows).rev() {
446//         for col in 0..count_columns {
447//             let mut border = table
448//                 .get_config()
449//                 .get_border((i - 1, col), (count_rows, count_columns));
450
451//             if border.is_empty() {
452//                 continue;
453//             }
454
455//             if col > 0 {
456//                 // because we delete border and we set borders column by column we need to do this swap.
457
458//                 border.left_top_corner = border.left_bottom_corner;
459//                 border.left_bottom_corner = None;
460//             }
461
462//             table
463//                 .get_config_mut()
464//                 .remove_border((i - 1, col), count_columns);
465//             table.get_config_mut().set_border((i, col), border);
466//         }
467//     }
468// }
469
470// #[cfg(feature = "color")]
471// fn move_aside_border_colors<R>(table: &mut Table<R>, row: usize)
472// where
473//     R: Records,
474// {
475//     let count_rows = table.get_records().count_rows();
476//     if count_rows < 2 {
477//         return;
478//     }
479
480//     let count_columns = table.get_records().count_columns();
481
482//     for i in (row + 1..count_rows).rev() {
483//         for col in 0..count_columns {
484//             let mut border = table
485//                 .get_config()
486//                 .get_border_color((i - 1, col), (count_rows, count_columns))
487//                 .cloned();
488
489//             if border.is_empty() {
490//                 continue;
491//             }
492
493//             if col > 0 {
494//                 // because we delete border and we set borders column by column we need to do this swap.
495
496//                 border.left_top_corner = border.left_bottom_corner;
497//                 border.left_bottom_corner = None;
498//             }
499
500//             table
501//                 .get_config_mut()
502//                 .remove_border_color((i - 1, col), (count_rows, count_columns));
503//             table.get_config_mut().set_border_color((i, col), border);
504//         }
505//     }
506// }
507
508// fn move_right_borders<R>(table: &mut Table<R>, row: usize, col: usize)
509// where
510//     R: Records,
511// {
512//     let count_columns = table.get_records().count_columns();
513//     let count_rows = table.get_records().count_rows();
514//     if count_columns < 2 {
515//         return;
516//     }
517
518//     for col in (col + 1..count_columns).rev() {
519//         for row in row..count_rows {
520//             let mut border = table
521//                 .get_config()
522//                 .get_border((row, col - 1), (count_rows, count_columns));
523
524//             if border.is_empty() {
525//                 continue;
526//             }
527
528//             if row > 0 {
529//                 // because we delete border and we set borders column by column we need to do this swap.
530
531//                 border.left_top_corner = border.right_top_corner;
532//                 border.right_top_corner = None;
533//             }
534
535//             table
536//                 .get_config_mut()
537//                 .remove_border((row, col - 1), count_columns);
538//             table.get_config_mut().set_border((row, col), border);
539//         }
540//     }
541// }
542
543// #[cfg(feature = "color")]
544// fn move_right_border_colors<R>(table: &mut Table<R>, init_row: usize, col: usize)
545// where
546//     R: Records,
547// {
548//     let count_columns = table.get_records().count_columns();
549//     let count_rows = table.get_records().count_rows();
550//     if count_columns < 2 {
551//         return;
552//     }
553
554//     for col in (col + 1..count_columns).rev() {
555//         for row in init_row..count_rows {
556//             let mut border = table
557//                 .get_config()
558//                 .get_border_color((row, col - 1), (count_rows, count_columns))
559//                 .cloned();
560
561//             if border.is_empty() {
562//                 continue;
563//             }
564
565//             if row > 0 {
566//                 // because we delete border and we set borders column by column we need to do this swap.
567
568//                 border.left_top_corner = border.right_top_corner;
569//                 border.right_top_corner = None;
570//             }
571
572//             table
573//                 .get_config_mut()
574//                 .remove_border_color((row, col - 1), (count_rows, count_columns));
575//             table.get_config_mut().set_border_color((row, col), border);
576//         }
577//     }
578// }