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// }