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