1use crate::_private::NonExhaustive;
2use crate::TableStyle;
3use crate::selection::RowSelection;
4use crate::table::TableState;
5use crate::util::{fallback_select_style, revert_style, transfer_buffer};
6use crate::{LayoutWidth, TableContext, TableSelection};
7use rat_scrolled::{Scroll, ScrollArea, ScrollAreaState};
8use ratatui_core::buffer::Buffer;
9use ratatui_core::layout::{Constraint, Flex, Layout, Rect};
10use ratatui_core::style::Style;
11use ratatui_core::text::Text;
12use ratatui_core::widgets::{StatefulWidget, Widget};
13use ratatui_widgets::block::Block;
14use std::cmp::max;
15use std::marker::PhantomData;
16use std::rc::Rc;
17
18pub struct Table<'a, T: Copy, Selection = RowSelection> {
62 header: Vec<Text<'a>>,
63 footer: Vec<Text<'a>>,
64
65 data: Option<Box<dyn Iterator<Item = T> + 'a>>,
66
67 row_height_fn: Option<Box<dyn FnMut(T, &TableContext) -> u8 + 'a>>,
68 render_row_fn: Option<Box<dyn FnMut(T, Rect, &mut Buffer, &TableContext) -> bool + 'a>>,
69 render_cell_fn: Vec<Option<Box<dyn FnMut(T, Rect, &mut Buffer, &TableContext) + 'a>>>,
70
71 widths: Vec<Constraint>,
72 varying_heights: bool,
73 flex: Flex,
74 column_spacing: u16,
75 layout_width: LayoutWidth,
76
77 style: Style,
78 block: Option<Block<'a>>,
79 hscroll: Option<Scroll<'a>>,
80 vscroll: Option<Scroll<'a>>,
81 header_style: Option<Style>,
82 footer_style: Option<Style>,
83 focus_style: Option<Style>,
84
85 select_row_style: Option<Style>,
86 show_row_focus: bool,
87 select_column_style: Option<Style>,
88 show_column_focus: bool,
89 select_cell_style: Option<Style>,
90 show_cell_focus: bool,
91 select_header_style: Option<Style>,
92 show_header_focus: bool,
93 select_footer_style: Option<Style>,
94 show_footer_focus: bool,
95
96 _phantom: PhantomData<Selection>,
97}
98
99impl<'a, T: Copy, Selection> Default for Table<'a, T, Selection> {
100 fn default() -> Self {
101 Self {
102 header: Default::default(),
103 footer: Default::default(),
104 data: Default::default(),
105 row_height_fn: Default::default(),
106 render_row_fn: Default::default(),
107 render_cell_fn: Default::default(),
108 widths: Default::default(),
109 varying_heights: Default::default(),
110 flex: Default::default(),
111 column_spacing: Default::default(),
112 layout_width: Default::default(),
113 style: Default::default(),
114 block: Default::default(),
115 hscroll: Default::default(),
116 vscroll: Default::default(),
117 header_style: Default::default(),
118 footer_style: Default::default(),
119 focus_style: Default::default(),
120 select_row_style: Default::default(),
121 show_row_focus: true,
122 select_column_style: Default::default(),
123 show_column_focus: Default::default(),
124 select_cell_style: Default::default(),
125 show_cell_focus: Default::default(),
126 select_header_style: Default::default(),
127 show_header_focus: Default::default(),
128 select_footer_style: Default::default(),
129 show_footer_focus: Default::default(),
130 _phantom: Default::default(),
131 }
132 }
133}
134
135impl<'a, T: Copy, Selection> Table<'a, T, Selection> {
136 pub fn new() -> Self
138 where
139 Selection: Default,
140 {
141 Self::default()
142 }
143
144 #[inline]
146 pub fn data(mut self, data: impl IntoIterator<Item = T> + 'a) -> Self {
147 self.data = Some(Box::new(data.into_iter()));
148 self
149 }
150
151 #[inline]
153 pub fn row_height(mut self, row_height_fn: impl FnMut(T, &TableContext) -> u8 + 'a) -> Self {
154 self.row_height_fn = Some(Box::new(row_height_fn));
155 self
156 }
157
158 #[inline]
166 pub fn render_row(
167 mut self,
168 render_row_fn: impl FnMut(T, Rect, &mut Buffer, &TableContext) -> bool + 'a,
169 ) -> Self {
170 self.render_row_fn = Some(Box::new(render_row_fn));
171 self
172 }
173
174 #[inline]
183 pub fn render_cell(
184 mut self,
185 column: usize,
186 render_cell_fn: impl FnMut(T, Rect, &mut Buffer, &TableContext) + 'a,
187 ) -> Self {
188 while self.render_cell_fn.len() <= column {
189 self.render_cell_fn.push(None);
190 }
191 self.render_cell_fn[column] = Some(Box::new(render_cell_fn));
192 self
193 }
194
195 #[inline]
197 pub fn header<I>(mut self, header: I) -> Self
198 where
199 I: IntoIterator,
200 I::Item: Into<Text<'a>>,
201 {
202 self.header = header.into_iter().map(|v| v.into()).collect();
203 self
204 }
205
206 #[inline]
208 pub fn footer<I>(mut self, footer: I) -> Self
209 where
210 I: IntoIterator,
211 I::Item: Into<Text<'a>>,
212 {
213 self.footer = footer.into_iter().map(|v| v.into()).collect();
214 self
215 }
216
217 #[inline]
221 pub fn widths<I>(mut self, widths: I) -> Self
222 where
223 I: IntoIterator,
224 I::Item: Into<Constraint>,
225 {
226 self.widths = widths.into_iter().map(|v| v.into()).collect();
227 self
228 }
229
230 pub fn varying_heights(mut self, varying: bool) -> Self {
237 self.varying_heights = varying;
238 self
239 }
240
241 #[inline]
243 pub fn flex(mut self, flex: Flex) -> Self {
244 self.flex = flex;
245 self
246 }
247
248 #[inline]
250 pub fn column_spacing(mut self, spacing: u16) -> Self {
251 self.column_spacing = spacing;
252 self
253 }
254
255 #[inline]
260 pub fn layout_width(mut self, width: LayoutWidth) -> Self {
261 self.layout_width = width;
262 self
263 }
264
265 #[inline]
267 pub fn block(mut self, block: Block<'a>) -> Self {
268 self.block = Some(block);
269 self.block = self.block.map(|v| v.style(self.style));
270 self
271 }
272
273 pub fn border_style(mut self, style: Style) -> Self {
275 self.block = self.block.map(|v| v.border_style(style));
276 self
277 }
278
279 pub fn title_style(mut self, style: Style) -> Self {
281 self.block = self.block.map(|v| v.title_style(style));
282 self
283 }
284
285 pub fn scroll(mut self, scroll: Scroll<'a>) -> Self {
287 self.hscroll = Some(scroll.clone().override_horizontal());
288 self.vscroll = Some(scroll.override_vertical());
289 self
290 }
291
292 pub fn hscroll(mut self, scroll: Scroll<'a>) -> Self {
294 self.hscroll = Some(scroll.override_horizontal());
295 self
296 }
297
298 pub fn vscroll(mut self, scroll: Scroll<'a>) -> Self {
300 self.vscroll = Some(scroll.override_vertical());
301 self
302 }
303
304 #[inline]
306 pub fn styles(mut self, styles: TableStyle) -> Self {
307 self.style = styles.style;
308 if styles.block.is_some() {
309 self.block = styles.block;
310 }
311 if let Some(border_style) = styles.border_style {
312 self.block = self.block.map(|v| v.border_style(border_style));
313 }
314 if let Some(title_style) = styles.title_style {
315 self.block = self.block.map(|v| v.title_style(title_style));
316 }
317 self.block = self.block.map(|v| v.style(self.style));
318
319 if let Some(styles) = styles.scroll {
320 self.hscroll = self.hscroll.map(|v| v.styles(styles.clone()));
321 self.vscroll = self.vscroll.map(|v| v.styles(styles));
322 }
323 if styles.header.is_some() {
324 self.header_style = styles.header;
325 }
326 if styles.footer.is_some() {
327 self.footer_style = styles.footer;
328 }
329 if styles.select_row.is_some() {
330 self.select_row_style = styles.select_row;
331 }
332 self.show_row_focus = styles.show_row_focus;
333 if styles.select_column.is_some() {
334 self.select_column_style = styles.select_column;
335 }
336 self.show_column_focus = styles.show_column_focus;
337 if styles.select_cell.is_some() {
338 self.select_cell_style = styles.select_cell;
339 }
340 self.show_cell_focus = styles.show_cell_focus;
341 if styles.select_header.is_some() {
342 self.select_header_style = styles.select_header;
343 }
344 self.show_header_focus = styles.show_header_focus;
345 if styles.select_footer.is_some() {
346 self.select_footer_style = styles.select_footer;
347 }
348 self.show_footer_focus = styles.show_footer_focus;
349 if styles.focus_style.is_some() {
350 self.focus_style = styles.focus_style;
351 }
352 self
353 }
354
355 #[inline]
357 pub fn style(mut self, style: Style) -> Self {
358 self.style = style;
359 self.block = self.block.map(|v| v.style(self.style));
360 self
361 }
362
363 #[inline]
365 pub fn header_style(mut self, style: Option<Style>) -> Self {
366 self.header_style = style;
367 self
368 }
369
370 #[inline]
372 pub fn footer_style(mut self, style: Option<Style>) -> Self {
373 self.footer_style = style;
374 self
375 }
376
377 #[inline]
380 pub fn select_row_style(mut self, select_style: Option<Style>) -> Self {
381 self.select_row_style = select_style;
382 self
383 }
384
385 #[inline]
387 pub fn show_row_focus(mut self, show: bool) -> Self {
388 self.show_row_focus = show;
389 self
390 }
391
392 #[inline]
395 pub fn select_column_style(mut self, select_style: Option<Style>) -> Self {
396 self.select_column_style = select_style;
397 self
398 }
399
400 #[inline]
402 pub fn show_column_focus(mut self, show: bool) -> Self {
403 self.show_column_focus = show;
404 self
405 }
406
407 #[inline]
410 pub fn select_cell_style(mut self, select_style: Option<Style>) -> Self {
411 self.select_cell_style = select_style;
412 self
413 }
414
415 #[inline]
417 pub fn show_cell_focus(mut self, show: bool) -> Self {
418 self.show_cell_focus = show;
419 self
420 }
421
422 #[inline]
425 pub fn select_header_style(mut self, select_style: Option<Style>) -> Self {
426 self.select_header_style = select_style;
427 self
428 }
429
430 #[inline]
432 pub fn show_header_focus(mut self, show: bool) -> Self {
433 self.show_header_focus = show;
434 self
435 }
436
437 #[inline]
440 pub fn select_footer_style(mut self, select_style: Option<Style>) -> Self {
441 self.select_footer_style = select_style;
442 self
443 }
444
445 #[inline]
447 pub fn show_footer_focus(mut self, show: bool) -> Self {
448 self.show_footer_focus = show;
449 self
450 }
451
452 #[inline]
458 pub fn focus_style(mut self, focus_style: Option<Style>) -> Self {
459 self.focus_style = focus_style;
460 self
461 }
462
463 pub fn width(&self) -> u16 {
465 let sa = ScrollArea::new()
466 .style(self.style)
467 .block(self.block.as_ref())
468 .h_scroll(self.hscroll.as_ref())
469 .v_scroll(self.vscroll.as_ref());
470 let padding = sa.padding();
471 let width = total_width(self, 0);
472
473 width + padding.left + padding.right
474 }
475
476 pub fn height(&self) -> u16 {
478 let sa = ScrollArea::new()
479 .style(self.style)
480 .block(self.block.as_ref())
481 .h_scroll(self.hscroll.as_ref())
482 .v_scroll(self.vscroll.as_ref());
483 let padding = sa.padding();
484 let header = self
485 .header
486 .iter()
487 .map(|v| v.height() as u16)
488 .max()
489 .unwrap_or(0);
490 let footer = self
491 .footer
492 .iter()
493 .map(|v| v.height() as u16)
494 .max()
495 .unwrap_or(0);
496
497 1 + header + footer + padding.top + padding.bottom
498 }
499}
500
501fn total_width<T: Copy, Selection>(widget: &Table<'_, T, Selection>, area_width: u16) -> u16 {
502 match widget.layout_width {
503 LayoutWidth::Length(len) => len,
504 LayoutWidth::Columns => {
505 let mut width = 0;
506 for w in widget.widths.iter().copied() {
507 match w {
508 Constraint::Min(v) => width += v + widget.column_spacing,
509 Constraint::Max(v) => width += v + widget.column_spacing,
510 Constraint::Length(v) => width += v + widget.column_spacing,
511 Constraint::Percentage(p) => {
512 width += (((area_width as u32) * (p as u32)) / 100) as u16;
513 }
514 Constraint::Ratio(n, d) => {
515 width += (((area_width as u32) * n) / d) as u16;
516 }
517 Constraint::Fill(_) => {
518 width += 10;
520 }
521 }
522 }
523 max(width, area_width)
524 }
525 LayoutWidth::Area => area_width,
526 }
527}
528
529fn layout_columns<T: Copy, Selection>(
531 widget: &Table<'_, T, Selection>,
532 area_width: u16,
533) -> (u16, Rc<[Rect]>, Rc<[Rect]>) {
534 let width = total_width(widget, area_width);
535 let area = Rect::new(0, 0, width, 0);
536
537 let (layout, spacers) = Layout::horizontal(&widget.widths)
538 .flex(widget.flex)
539 .spacing(widget.column_spacing)
540 .split_with_spacers(area);
541
542 (width, layout, spacers)
543}
544
545#[inline]
547fn layout_areas<T: Copy, Selection>(widget: &Table<'_, T, Selection>, area: Rect) -> Rc<[Rect]> {
548 let heights = vec![
549 Constraint::Length(
550 widget
551 .header
552 .iter()
553 .map(|v| v.height() as u16)
554 .max()
555 .unwrap_or(0),
556 ),
557 Constraint::Fill(1),
558 Constraint::Length(
559 widget
560 .footer
561 .iter()
562 .map(|v| v.height() as u16)
563 .max()
564 .unwrap_or(0),
565 ),
566 ];
567
568 Layout::vertical(heights).split(area)
569}
570
571impl<'a, T: Copy, Selection> StatefulWidget for Table<'a, T, Selection>
572where
573 T: 'a,
574 Selection: TableSelection + Default,
575{
576 type State = TableState<Selection>;
577
578 fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
579 let data = self.data.take();
580 if let Some(mut data) = data {
581 render_table(self, Some(data.as_mut()), area, buf, state);
582 } else {
583 render_table(self, None, area, buf, state);
584 }
585 }
586}
587
588fn render_table<'a, T: Copy, Selection: TableSelection>(
589 mut widget: Table<'a, T, Selection>,
590 mut data: Option<&mut dyn Iterator<Item = T>>,
591 area: Rect,
592 buf: &mut Buffer,
593 state: &mut TableState<Selection>,
594) {
595 state.rows = data.as_mut().map(|v| v.size_hint().0).unwrap_or(0);
596 state.columns = widget.widths.len();
597 state.area = area;
598
599 let sa = ScrollArea::new()
600 .style(widget.style)
601 .block(widget.block.as_ref())
602 .h_scroll(widget.hscroll.as_ref())
603 .v_scroll(widget.vscroll.as_ref());
604 state.inner = sa.inner(area, Some(&state.hscroll), Some(&state.vscroll));
605
606 let l_rows = layout_areas(&widget, state.inner);
607 state.header_area = l_rows[0];
608 state.table_area = l_rows[1];
609 state.footer_area = l_rows[2];
610
611 let (width, l_columns, l_spacers) = layout_columns(&widget, state.table_area.width);
613 calculate_column_areas(state.columns, l_columns.as_ref(), l_spacers.as_ref(), state);
614
615 sa.render_block(area, buf);
617
618 render_header(
620 &widget,
621 state.columns,
622 width,
623 l_columns.as_ref(),
624 l_spacers.as_ref(),
625 state.header_area,
626 buf,
627 state,
628 );
629 render_footer(
630 &widget,
631 state.columns,
632 width,
633 l_columns.as_ref(),
634 l_spacers.as_ref(),
635 state.footer_area,
636 buf,
637 state,
638 );
639
640 state.row_areas.clear();
642 state.row_heights.clear();
643 state.vscroll.set_page_len(0);
644 state.hscroll.set_page_len(area.width as usize);
645
646 let mut ctx = TableContext {
647 row: 0,
648 focus: state.focus.get(),
649 selected_cell: false,
650 selected_row: false,
651 selected_column: false,
652 style: widget.style,
653 select_style: None,
654 column_layout: &state.column_layout,
655 space_area: Default::default(),
656 row_area: Default::default(),
657 non_exhaustive: NonExhaustive,
658 };
659
660 'll: {
661 let Some(data) = data else {
662 break 'll;
663 };
664
665 let mut row = None;
667 let mut value;
668 let mut row_y = state.table_area.y;
669 let mut row_heights = Vec::new();
670 let mut row_buf = Buffer::empty(Rect::new(0, 0, width, 1));
671
672 if widget.varying_heights {
674 loop {
675 value = if let Some(v) = data.nth(0) {
676 v
677 } else {
678 break 'll;
679 };
680
681 if let Some(rr) = row {
682 if rr + 1 < state.vscroll.offset() {
683 ctx.row = rr;
684 let row_height = widget
685 .row_height_fn
686 .as_mut()
687 .map_or(1, |f| (*f)(value, &ctx));
688 state.row_heights.push(row_height, true)
689 }
690 row = Some(rr + 1);
691 } else {
692 if 0 < state.vscroll.offset() {
693 ctx.row = 0;
694 let row_height = widget
695 .row_height_fn
696 .as_mut()
697 .map_or(1, |f| (*f)(value, &ctx));
698 state.row_heights.push(row_height, true)
699 }
700 row = Some(0);
701 }
702
703 if row.expect("row") >= state.vscroll.offset() {
704 break;
705 }
706 }
707 } else {
708 value = if let Some(v) = data.nth(state.vscroll.offset()) {
709 v
710 } else {
711 break 'll;
712 };
713 row = Some(state.vscroll.offset());
714 }
715
716 loop {
718 ctx.row = row.expect("row");
719 ctx.selected_cell = false;
720 ctx.selected_row = false;
721 ctx.selected_column = false;
722 ctx.select_style = None;
723 ctx.focus = false;
724 ctx.row_area = Rect::default();
725 ctx.space_area = Rect::default();
726
727 let row_height = widget
729 .row_height_fn
730 .as_mut()
731 .map_or(1, |f| (*f)(value, &ctx)) as u16;
732 let render_row_area = Rect::new(0, 0, width, row_height);
733 ctx.row_area = render_row_area;
734 row_buf.resize(render_row_area);
735 row_heights.push(row_height);
736 state
737 .row_heights
738 .push(row_height as u8, widget.varying_heights);
739 let visible_row_area = Rect::new(
741 state.table_area.x,
742 row_y,
743 state.table_area.width,
744 render_row_area.height,
745 )
746 .intersection(state.table_area);
747 state.row_areas.push(visible_row_area);
748 state.vscroll.page_len += 1;
749
750 if state.selection.is_selected_row(row.expect("row")) {
751 ctx.selected_row = true;
752 ctx.select_style = patch_select(
753 widget.select_row_style,
754 widget.focus_style,
755 state.focus.get(),
756 widget.show_row_focus,
757 );
758 }
759
760 let render_cells = if render_row_area.height > 0 {
761 if let Some(row_style) = ctx.select_style {
762 row_buf.set_style(render_row_area, row_style);
763 } else {
764 row_buf.set_style(render_row_area, widget.style);
765 }
766
767 if let Some(render_row) = widget.render_row_fn.as_mut() {
768 render_row(value, render_row_area, &mut row_buf, &ctx)
769 } else {
770 true
771 }
772 } else {
773 true
774 };
775
776 if render_cells && render_row_area.height > 0 {
778 let mut col = 0;
779 loop {
780 if col >= state.columns {
781 break;
782 }
783
784 ctx.selected_cell = false;
785 ctx.selected_column = false;
786 ctx.select_style = None;
787
788 let render_cell_area = Rect::new(
789 l_columns[col].x,
790 0,
791 l_columns[col].width,
792 render_row_area.height,
793 );
794 ctx.space_area = Rect::new(
795 l_spacers[col + 1].x,
796 0,
797 l_spacers[col + 1].width,
798 render_row_area.height,
799 );
800
801 if state.selection.is_selected_column(col) {
802 ctx.selected_column = true;
803 ctx.select_style = patch_select(
804 widget.select_column_style,
805 widget.focus_style,
806 state.focus.get(),
807 widget.show_column_focus,
808 );
809 }
810
811 if state.selection.is_selected_cell(col, row.expect("row")) {
812 ctx.selected_cell = true;
813 ctx.select_style = patch_select(
814 widget.select_cell_style,
815 widget.focus_style,
816 state.focus.get(),
817 widget.show_cell_focus,
818 );
819 }
820
821 if render_cell_area.right() > state.hscroll.offset as u16
823 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
824 {
825 if let Some(select_style) = ctx.select_style {
826 row_buf.set_style(render_cell_area, select_style);
827 row_buf.set_style(ctx.space_area, select_style);
828 }
829
830 if let Some(Some(render_cell)) = widget.render_cell_fn.get_mut(col) {
831 render_cell(value, render_cell_area, &mut row_buf, &ctx);
832 }
833 }
834
835 col += 1;
836 }
837
838 transfer_buffer(
841 &mut row_buf,
842 state.hscroll.offset() as u16,
843 visible_row_area,
844 buf,
845 );
846 }
847
848 value = if let Some(value) = data.nth(0) {
849 value
850 } else {
851 break 'll;
852 };
853
854 row = Some(row.expect("row").saturating_add(1));
855 row_y += render_row_area.height;
856
857 if visible_row_area.bottom() >= state.table_area.bottom() {
858 break;
859 }
860 }
861
862 if !widget.varying_heights {
863 let skip_rows = state
867 .rows
868 .saturating_sub(row.map_or(0, |v| v + 1))
869 .saturating_sub(state.table_area.height as usize);
870 if skip_rows > 0 {
872 row_heights.clear();
873 }
874
875 value = if let Some(value) = data.nth(skip_rows) {
876 row = Some(row.map_or(skip_rows, |row| row + skip_rows + 1));
877 value
878 } else {
879 break 'll;
880 }
881 }
882
883 let mut sum_height = row_heights.iter().sum::<u16>();
884 loop {
885 let row_height = widget
886 .row_height_fn
887 .as_mut()
888 .map_or(1, |f| (*f)(value, &ctx)) as u16;
889 state
890 .row_heights
891 .push(row_height as u8, widget.varying_heights);
892 row_heights.push(row_height);
893
894 sum_height += row_height;
897 if sum_height.saturating_sub(row_heights.first().copied().unwrap_or_default())
898 > state.table_area.height
899 {
900 let lost_height = row_heights.remove(0);
901 sum_height -= lost_height;
902 }
903
904 value = if let Some(value) = data.nth(0) {
905 value
906 } else {
907 break;
908 };
909
910 row = Some(row.expect("row") + 1);
911 }
912
913 state.rows = row.map_or(0, |v| v + 1);
914 if let Some(last_page) = calc_last_page(state.table_area.height, row_heights) {
916 state.vscroll.set_max_offset(state.rows - last_page);
917 } else {
918 state
922 .vscroll
923 .set_max_offset(state.rows.saturating_sub(state.table_area.height as usize));
924 }
925 state
926 .hscroll
927 .set_max_offset(width.saturating_sub(state.table_area.width) as usize);
928 state.selection.validate_rows(state.rows);
929 state.selection.validate_cols(state.columns);
930 }
931
932 ScrollArea::new()
934 .style(widget.style)
935 .block(widget.block.as_ref())
936 .h_scroll(widget.hscroll.as_ref())
937 .v_scroll(widget.vscroll.as_ref())
938 .render_scrollbars(
939 area,
940 buf,
941 &mut ScrollAreaState::new()
942 .h_scroll(&mut state.hscroll)
943 .v_scroll(&mut state.vscroll),
944 );
945}
946
947#[allow(clippy::too_many_arguments)]
948fn render_footer<T: Copy, Selection: TableSelection>(
949 widget: &Table<'_, T, Selection>,
950 columns: usize,
951 width: u16,
952 l_columns: &[Rect],
953 l_spacers: &[Rect],
954 area: Rect,
955 buf: &mut Buffer,
956 state: &mut TableState<Selection>,
957) {
958 let height = widget
959 .footer
960 .iter()
961 .map(|v| v.height() as u16)
962 .max()
963 .unwrap_or(0);
964
965 if height > 0 {
966 let render_row_area = Rect::new(0, 0, width, height);
967 let mut row_buf = Buffer::empty(render_row_area);
968
969 row_buf.set_style(render_row_area, widget.style);
970 if let Some(footer_style) = widget.footer_style {
971 row_buf.set_style(render_row_area, footer_style);
972 }
973
974 for (col, text) in widget.footer.iter().enumerate() {
975 if col >= columns {
976 break;
977 }
978
979 let render_cell_area =
980 Rect::new(l_columns[col].x, 0, l_columns[col].width, area.height);
981 let render_space_area = Rect::new(
982 l_spacers[col + 1].x,
983 0,
984 l_spacers[col + 1].width,
985 area.height,
986 );
987
988 if state.selection.is_selected_column(col) {
989 if let Some(selected_style) = patch_select(
990 widget.select_footer_style,
991 widget.focus_style,
992 state.focus.get(),
993 widget.show_footer_focus,
994 ) {
995 row_buf.set_style(render_cell_area, selected_style);
996 row_buf.set_style(render_space_area, selected_style);
997 }
998 };
999
1000 if render_cell_area.right() > state.hscroll.offset as u16
1002 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1003 {
1004 text.render(render_cell_area, &mut row_buf);
1005 }
1006 }
1007
1008 transfer_buffer(&mut row_buf, state.hscroll.offset() as u16, area, buf);
1010 }
1011}
1012
1013#[allow(clippy::too_many_arguments)]
1014fn render_header<T: Copy, Selection: TableSelection>(
1015 widget: &Table<'_, T, Selection>,
1016 columns: usize,
1017 width: u16,
1018 l_columns: &[Rect],
1019 l_spacers: &[Rect],
1020 area: Rect,
1021 buf: &mut Buffer,
1022 state: &mut TableState<Selection>,
1023) {
1024 let height = widget
1025 .header
1026 .iter()
1027 .map(|v| v.height() as u16)
1028 .max()
1029 .unwrap_or(0);
1030
1031 if height > 0 {
1032 let render_row_area = Rect::new(0, 0, width, height);
1033 let mut row_buf = Buffer::empty(render_row_area);
1034
1035 row_buf.set_style(render_row_area, widget.style);
1036 if let Some(header_style) = widget.header_style {
1037 row_buf.set_style(render_row_area, header_style);
1038 }
1039
1040 for (col, text) in widget.header.iter().enumerate() {
1041 if col >= columns {
1042 break;
1043 }
1044
1045 let render_cell_area =
1046 Rect::new(l_columns[col].x, 0, l_columns[col].width, area.height);
1047 let render_space_area = Rect::new(
1048 l_spacers[col + 1].x,
1049 0,
1050 l_spacers[col + 1].width,
1051 area.height,
1052 );
1053
1054 if state.selection.is_selected_column(col) {
1055 if let Some(selected_style) = patch_select(
1056 widget.select_header_style,
1057 widget.focus_style,
1058 state.focus.get(),
1059 widget.show_header_focus,
1060 ) {
1061 row_buf.set_style(render_cell_area, selected_style);
1062 row_buf.set_style(render_space_area, selected_style);
1063 }
1064 };
1065
1066 if render_cell_area.right() > state.hscroll.offset as u16
1068 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1069 {
1070 text.render(render_cell_area, &mut row_buf);
1071 }
1072 }
1073
1074 transfer_buffer(&mut row_buf, state.hscroll.offset() as u16, area, buf);
1076 }
1077}
1078
1079fn calculate_column_areas<Selection>(
1080 columns: usize,
1081 l_columns: &[Rect],
1082 l_spacers: &[Rect],
1083 state: &mut TableState<Selection>,
1084) {
1085 state.column_areas.clear();
1086 state.column_layout.clear();
1087
1088 let mut col = 0;
1089 let shift = state.hscroll.offset() as isize;
1090 loop {
1091 if col >= columns {
1092 break;
1093 }
1094
1095 state.column_layout.push(Rect::new(
1096 l_columns[col].x,
1097 0,
1098 l_columns[col].width + l_spacers[col + 1].width,
1099 0,
1100 ));
1101
1102 let cell_x1 = l_columns[col].x as isize;
1103 let cell_x2 = (l_columns[col].x + l_columns[col].width + l_spacers[col + 1].width) as isize;
1104
1105 let squish_x1 = cell_x1.saturating_sub(shift);
1106 let squish_x2 = cell_x2.saturating_sub(shift);
1107
1108 let abs_x1 = max(0, squish_x1) as u16;
1109 let abs_x2 = max(0, squish_x2) as u16;
1110
1111 let v_area = Rect::new(
1112 state.table_area.x + abs_x1,
1113 state.table_area.y,
1114 abs_x2 - abs_x1,
1115 state.table_area.height,
1116 );
1117 state
1118 .column_areas
1119 .push(v_area.intersection(state.table_area));
1120
1121 col += 1;
1122 }
1123}
1124
1125fn patch_select(
1126 style: Option<Style>,
1127 focus_style: Option<Style>,
1128 focus: bool,
1129 show_focus: bool,
1130) -> Option<Style> {
1131 if let Some(style) = style {
1132 if let Some(focus_style) = focus_style {
1133 if focus && show_focus {
1134 Some(style.patch(focus_style))
1135 } else {
1136 Some(fallback_select_style(style))
1137 }
1138 } else {
1139 if focus && show_focus {
1140 Some(revert_style(style))
1141 } else {
1142 Some(fallback_select_style(style))
1143 }
1144 }
1145 } else {
1146 None
1147 }
1148}
1149
1150fn calc_last_page(height: u16, mut row_heights: Vec<u16>) -> Option<usize> {
1151 let mut sum_heights = 0;
1152 let mut n_rows = 0;
1153 while let Some(h) = row_heights.pop() {
1154 sum_heights += h;
1155 n_rows += 1;
1156 if sum_heights >= height {
1157 break;
1158 }
1159 }
1160
1161 if sum_heights < height {
1162 None
1163 } else {
1164 Some(n_rows)
1165 }
1166}