@ -8,10 +8,11 @@ use std::convert::From;
use tui ::{
backend ::Backend ,
layout ::{ Constraint , Direction , Layout , Rect } ,
style ::{ Color , Style} ,
style ::{ Color , Modifier, Style} ,
widgets ::{ Block , Borders , Cell , Row , Table , TableState } ,
Frame ,
} ;
use unicode_width ::UnicodeWidthStr ;
pub struct TableComponent {
pub state : TableState ,
@ -19,6 +20,7 @@ pub struct TableComponent {
pub rows : Vec < Vec < String > > ,
pub column_index : usize ,
pub column_page : usize ,
pub column_page_start : std ::cell ::Cell < usize > ,
pub scroll : VerticalScroll ,
pub select_entire_row : bool ,
pub eod : bool ,
@ -31,7 +33,8 @@ impl Default for TableComponent {
headers : vec ! [ ] ,
rows : vec ! [ ] ,
column_page : 0 ,
column_index : 1 ,
column_index : 0 ,
column_page_start : std ::cell ::Cell ::new ( 0 ) ,
scroll : VerticalScroll ::new ( ) ,
select_entire_row : false ,
eod : false ,
@ -107,11 +110,7 @@ impl TableComponent {
if self . rows . is_empty ( ) {
return ;
}
if self . column_index = = self . headers . len ( ) {
return ;
}
if self . column_index = = 9 {
self . next_column_page ( ) ;
if self . column_index > = self . headers ( ) . len ( ) . saturating_sub ( 1 ) {
return ;
}
self . select_entire_row = false ;
@ -122,58 +121,161 @@ impl TableComponent {
if self . rows . is_empty ( ) {
return ;
}
if self . column_index = = 1 {
self . previous_column_page ( ) ;
if self . column_index = = 0 {
return ;
}
self . select_entire_row = false ;
self . column_index - = 1 ;
}
pub fn next_column_page ( & mut self ) {
if self . headers . len ( ) > 9 & & self . column_page < self . headers . len ( ) - 9 {
self . column_page + = 1
}
}
pub fn previous_column_page ( & mut self ) {
if self . column_page > 0 {
self . column_page - = 1
}
}
pub fn is_selected_cell ( & self , row_index : usize , column_index : usize ) -> bool {
if column_index ! = self . column_index {
return false ;
}
matches! ( self . state . selected ( ) , Some ( selected_row_index ) if row_index = = selected_row_index )
pub fn is_row_number_clumn ( & self , row_index : usize , column_index : usize ) -> bool {
matches! ( self . state . selected ( ) , Some ( selected_row_index ) if row_index = = selected_row_index & & 0 = = column_index )
}
pub fn selected_cell ( & self ) -> Option < String > {
self . rows
. get ( self . state . selected ( ) ? ) ?
. get ( self . column_index .saturating_sub ( 1 ) + self . column_page )
. get ( self . column_index )
. map ( | cell | cell . to_string ( ) )
}
pub fn headers ( & self ) -> Vec < String > {
let mut headers = self . headers [ self . column_page .. ] . to_vec ( ) ;
self . headers . clone ( )
}
pub fn headers_with_number ( & self , left : usize , right : usize ) -> Vec < String > {
let mut headers = self . headers . clone ( ) [ left .. right ] . to_vec ( ) ;
headers . insert ( 0 , "" . to_string ( ) ) ;
headers
}
pub fn rows ( & self ) -> Vec < Vec < String > > {
self . rows . clone ( )
}
pub fn rows_with_number ( & self , left : usize , right : usize ) -> Vec < Vec < String > > {
let rows = self
. rows
. iter ( )
. map ( | row | row [ self . column_page .. ] . to_vec ( ) )
. map ( | row | row .to_vec ( ) )
. collect ::< Vec < Vec < String > > > ( ) ;
let mut new_rows = rows ;
let mut new_rows : Vec < Vec < String > > =
rows . iter ( ) . map ( | row | row [ left .. right ] . to_vec ( ) ) . collect ( ) ;
for ( index , row ) in new_rows . iter_mut ( ) . enumerate ( ) {
row . insert ( 0 , ( index + 1 ) . to_string ( ) )
}
new_rows
}
pub fn calculate_widths (
& self ,
area_width : u16 ,
) -> ( usize , Vec < String > , Vec < Vec < String > > , Vec < Constraint > ) {
if self . rows . is_empty ( ) {
return ( 0 , Vec ::new ( ) , Vec ::new ( ) , Vec ::new ( ) ) ;
}
if self . column_index < self . column_page_start . get ( ) {
self . column_page_start . set ( self . column_index ) ;
}
let right_column_index = self . column_index . clone ( ) ;
let mut column_index = self . column_index ;
let number_clomn_width = ( self . rows . len ( ) + 1 ) . to_string ( ) . width ( ) as u16 ;
let mut widths = vec! [ ] ;
loop {
let length = self
. rows ( )
. iter ( )
. map ( | row | {
row . get ( column_index )
. map_or ( String ::new ( ) , | cell | cell . to_string ( ) )
. width ( )
} )
. collect ::< Vec < usize > > ( )
. iter ( )
. max ( )
. map_or ( 3 , | v | {
* v . max (
& self
. headers ( )
. get ( column_index )
. map_or ( 3 , | header | header . to_string ( ) . width ( ) ) ,
)
. clamp ( & 3 , & 20 ) as u16
} ) ;
if widths . iter ( ) . map ( | ( _ , width ) | width ) . sum ::< u16 > ( ) + length
> area_width
. saturating_sub ( 7 )
. saturating_sub ( number_clomn_width )
{
column_index + = 1 ;
break ;
}
widths . push ( ( self . headers [ column_index ] . clone ( ) , length ) ) ;
if column_index = = self . column_page_start . get ( ) {
break ;
}
column_index - = 1 ;
}
let left_column_index = column_index ;
widths . reverse ( ) ;
let selected_column_index = widths . len ( ) . saturating_sub ( 1 ) ;
let mut column_index = right_column_index + 1 ;
while widths . iter ( ) . map ( | ( _ , width ) | width ) . sum ::< u16 > ( )
< = area_width
. saturating_sub ( 7 )
. saturating_sub ( number_clomn_width )
{
let length = self
. rows ( )
. iter ( )
. map ( | row | {
row . get ( column_index )
. map_or ( String ::new ( ) , | cell | cell . to_string ( ) )
. width ( )
} )
. collect ::< Vec < usize > > ( )
. iter ( )
. max ( )
. map_or ( 3 , | v | {
* v . max (
self . headers ( )
. iter ( )
. map ( | header | header . to_string ( ) . width ( ) )
. collect ::< Vec < usize > > ( )
. get ( column_index )
. unwrap_or ( & 3 ) ,
)
. clamp ( & 3 , & 20 ) as u16
} ) ;
match self . headers . get ( column_index ) {
Some ( header ) = > {
widths . push ( ( header . to_string ( ) , length ) ) ;
}
None = > break ,
}
column_index + = 1
}
if self . column_index ! = self . headers . len ( ) . saturating_sub ( 1 ) {
widths . pop ( ) ;
}
let right_column_index = column_index ;
let mut constraints = widths
. iter ( )
. map ( | ( _ , width ) | Constraint ::Length ( * width ) )
. collect ::< Vec < Constraint > > ( ) ;
if self . column_index ! = self . headers . len ( ) . saturating_sub ( 1 ) {
constraints . push ( Constraint ::Min ( 10 ) ) ;
}
constraints . insert ( 0 , Constraint ::Length ( number_clomn_width ) ) ;
self . column_page_start . set ( left_column_index ) ;
(
selected_column_index + 1 ,
self . headers_with_number ( left_column_index , right_column_index ) ,
self . rows_with_number ( left_column_index , right_column_index ) ,
constraints ,
)
}
}
impl DrawableComponent for TableComponent {
@ -199,12 +301,17 @@ impl DrawableComponent for TableComponent {
TableValueComponent ::new ( self . selected_cell ( ) . unwrap_or_default ( ) )
. draw ( f , layout [ 0 ] , focused ) ? ;
let headers = self . headers ( ) ;
let header_cells = headers
. iter ( )
. map ( | h | Cell ::from ( h . to_string ( ) ) . style ( Style ::default ( ) ) ) ;
let block = Block ::default ( ) . borders ( Borders ::ALL ) . title ( "Records" ) ;
let ( selected_column_index , headers , rows , constraints ) =
self . calculate_widths ( block . inner ( layout [ 1 ] ) . width ) ;
let header_cells = headers . iter ( ) . enumerate ( ) . map ( | ( column_index , h ) | {
Cell ::from ( h . to_string ( ) ) . style ( if selected_column_index = = column_index {
Style ::default ( ) . add_modifier ( Modifier ::BOLD )
} else {
Style ::default ( )
} )
} ) ;
let header = Row ::new ( header_cells ) . height ( 1 ) . bottom_margin ( 1 ) ;
let rows = self . rows ( ) ;
let rows = rows . iter ( ) . enumerate ( ) . map ( | ( row_index , item ) | {
let height = item
. iter ( )
@ -213,20 +320,20 @@ impl DrawableComponent for TableComponent {
. unwrap_or ( 0 )
+ 1 ;
let cells = item . iter ( ) . enumerate ( ) . map ( | ( column_index , c ) | {
Cell ::from ( c . to_string ( ) ) . style ( if self . is_selected_cell ( row_index , column_index ) {
Cell ::from ( c . to_string ( ) ) . style ( if matches! ( self . state . selected ( ) , Some ( selected_row_index ) if row_index = = selected_row_index & & selected_column_index = = column_index ) {
Style ::default ( ) . bg ( Color ::Blue )
} else if self . is_row_number_clumn ( row_index , column_index ) {
Style ::default ( ) . add_modifier ( Modifier ::BOLD )
} else {
Style ::default ( )
} )
} ) ;
Row ::new ( cells ) . height ( height as u16 ) . bottom_margin ( 1 )
} ) ;
let widths = ( 0 .. 10 )
. map ( | _ | Constraint ::Percentage ( 10 ) )
. collect ::< Vec < Constraint > > ( ) ;
let table = Table ::new ( rows )
. header ( header )
. block ( Block::default ( ) . borders ( Borders ::ALL ) . title ( "Records" ) )
. block ( block )
. highlight_style ( if self . select_entire_row {
Style ::default ( ) . bg ( Color ::Blue )
} else {
@ -237,7 +344,7 @@ impl DrawableComponent for TableComponent {
} else {
Style ::default ( ) . fg ( Color ::DarkGray )
} )
. widths ( & width s) ;
. widths ( & constraint s) ;
f . render_stateful_widget ( table , layout [ 1 ] , & mut self . state ) ;
self . scroll . draw ( f , layout [ 1 ] ) ;
@ -293,12 +400,13 @@ impl Component for TableComponent {
#[ cfg(test) ]
mod test {
use super ::TableComponent ;
use tui ::layout ::Constraint ;
#[ test ]
fn test_headers ( ) {
let mut component = TableComponent ::default ( ) ;
component . headers = vec! [ "a" , "b" , "c" ] . iter ( ) . map ( | h | h . to_string ( ) ) . collect ( ) ;
assert_eq! ( component . headers ( ) , vec! [ "" , " a", " b", "c "] )
assert_eq! ( component . headers _with_number ( 1 , 2 ) , vec! [ "" , " b"] )
}
#[ test ]
@ -309,8 +417,53 @@ mod test {
vec! [ "d" , "e" , "f" ] . iter ( ) . map ( | h | h . to_string ( ) ) . collect ( ) ,
] ;
assert_eq! (
component . rows ( ) ,
vec! [ vec! [ "1" , " a", " b", "c "] , vec! [ "2" , " d", " e", "f "] ] ,
component . rows _with_number ( 1 , 2 ) ,
vec! [ vec! [ "1" , " b"] , vec! [ "2" , " e"] ] ,
)
}
#[ test ]
fn test_calculate_widths ( ) {
let mut component = TableComponent ::default ( ) ;
component . headers = vec! [ "1" , "2" , "3" ] . iter ( ) . map ( | h | h . to_string ( ) ) . collect ( ) ;
component . rows = vec! [
vec! [ "aaaaa" , "bbbbb" , "ccccc" ]
. iter ( )
. map ( | h | h . to_string ( ) )
. collect ( ) ,
vec! [ "d" , "e" , "f" ] . iter ( ) . map ( | h | h . to_string ( ) ) . collect ( ) ,
] ;
let ( selected_column_index , headers , rows , constraints ) = component . calculate_widths ( 17 ) ;
assert_eq! ( selected_column_index , 1 ) ;
assert_eq! ( headers , vec! [ "" , "1" , "2" ] ) ;
assert_eq! ( rows , vec! [ vec! [ "1" , "aaaaa" , "bbbbb" ] , vec! [ "2" , "d" , "e" ] ] ) ;
assert_eq! (
constraints ,
vec! [
Constraint ::Length ( 1 ) ,
Constraint ::Length ( 5 ) ,
Constraint ::Min ( 10 ) ,
]
) ;
let ( selected_column_index , headers , rows , constraints ) = component . calculate_widths ( 27 ) ;
assert_eq! ( selected_column_index , 1 ) ;
assert_eq! ( headers , vec! [ "" , "1" , "2" , "3" ] ) ;
assert_eq! (
rows ,
vec! [
vec! [ "1" , "aaaaa" , "bbbbb" , "ccccc" ] ,
vec! [ "2" , "d" , "e" , "f" ]
]
) ;
assert_eq! (
constraints ,
vec! [
Constraint ::Length ( 1 ) ,
Constraint ::Length ( 5 ) ,
Constraint ::Length ( 5 ) ,
Constraint ::Min ( 10 ) ,
]
) ;
}
}