@ -1,4 +1,3 @@
use std ::convert ::AsRef ;
use std ::iter ::{ self , Iterator } ;
use unicode_width ::UnicodeWidthStr ;
@ -6,16 +5,64 @@ use unicode_width::UnicodeWidthStr;
use crate ::buffer ::Buffer ;
use crate ::layout ::{ Corner , Rect } ;
use crate ::style ::Style ;
use crate ::widgets ::{ Block , Text, Widget } ;
use crate ::widgets ::{ Block , StatefulWidget, Text, Widget } ;
pub struct ListState {
offset : usize ,
selected : Option < usize > ,
}
impl Default for ListState {
fn default ( ) -> ListState {
ListState {
offset : 0 ,
selected : None ,
}
}
}
impl ListState {
pub fn selected ( & self ) -> Option < usize > {
self . selected
}
pub fn select ( & mut self , index : Option < usize > ) {
self . selected = index ;
if index . is_none ( ) {
self . offset = 0 ;
}
}
}
/// A widget to display several items among which one can be selected (optional)
///
/// # Examples
///
/// ```
/// # use tui::widgets::{Block, Borders, List, Text};
/// # use tui::style::{Style, Color, Modifier};
/// # fn main() {
/// let items = ["Item 1", "Item 2", "Item 3"].iter().map(|i| Text::raw(*i));
/// List::new(items)
/// .block(Block::default().title("List").borders(Borders::ALL))
/// .style(Style::default().fg(Color::White))
/// .highlight_style(Style::default().modifier(Modifier::ITALIC))
/// .highlight_symbol(">>");
/// # }
/// ```
pub struct List < ' b , L >
where
L : Iterator < Item = Text < ' b > > ,
{
block : Option < Block < ' b > > ,
items : L ,
style : Style ,
start_corner : Corner ,
/// Base style of the widget
style : Style ,
/// Style used to render selected item
highlight_style : Style ,
/// Symbol in front of the selected item (Shift all items to the right)
highlight_symbol : Option < & ' b str > ,
}
impl < ' b , L > Default for List < ' b , L >
@ -28,6 +75,8 @@ where
items : L ::default ( ) ,
style : Default ::default ( ) ,
start_corner : Corner ::TopLeft ,
highlight_style : Style ::default ( ) ,
highlight_symbol : None ,
}
}
}
@ -42,6 +91,8 @@ where
items ,
style : Default ::default ( ) ,
start_corner : Corner ::TopLeft ,
highlight_style : Style ::default ( ) ,
highlight_symbol : None ,
}
}
@ -63,20 +114,32 @@ where
self
}
pub fn highlight_symbol ( mut self , highlight_symbol : & ' b str ) -> List < ' b , L > {
self . highlight_symbol = Some ( highlight_symbol ) ;
self
}
pub fn highlight_style ( mut self , highlight_style : Style ) -> List < ' b , L > {
self . highlight_style = highlight_style ;
self
}
pub fn start_corner ( mut self , corner : Corner ) -> List < ' b , L > {
self . start_corner = corner ;
self
}
}
impl < ' b , L > Widget for List < ' b , L >
impl < ' b , L > Stateful Widget for List < ' b , L >
where
L : Iterator < Item = Text < ' b > > ,
{
fn draw ( & mut self , area : Rect , buf : & mut Buffer ) {
type State = ListState ;
fn render ( mut self , area : Rect , buf : & mut Buffer , state : & mut Self ::State ) {
let list_area = match self . block {
Some ( ref mut b ) = > {
b . draw ( area , buf ) ;
b . ren de r( area , buf ) ;
b . inner ( area )
}
None = > area ,
@ -86,11 +149,36 @@ where
return ;
}
self . background ( list_area , buf , self . style . bg ) ;
let list_height = list_area . height as usize ;
buf . set_background ( list_area , self . style . bg ) ;
// Use highlight_style only if something is selected
let ( selected , highlight_style ) = match state . selected {
Some ( i ) = > ( Some ( i ) , self . highlight_style ) ,
None = > ( None , self . style ) ,
} ;
let highlight_symbol = self . highlight_symbol . unwrap_or ( "" ) ;
let blank_symbol = iter ::repeat ( " " )
. take ( highlight_symbol . width ( ) )
. collect ::< String > ( ) ;
// Make sure the list show the selected item
state . offset = if let Some ( selected ) = selected {
if selected > = list_height + state . offset - 1 {
selected + 1 - list_height
} else if selected < state . offset {
selected
} else {
state . offset
}
} else {
0
} ;
for ( i , item ) in self
. items
. by_ref ( )
. skip( state . offset )
. enumerate ( )
. take ( list_area . height as usize )
{
@ -100,144 +188,53 @@ where
// Not supported
_ = > ( list_area . left ( ) , list_area . top ( ) + i as u16 ) ,
} ;
let ( x , style ) = if let Some ( s ) = selected {
if s = = i + state . offset {
let ( x , _ ) = buf . set_stringn (
x ,
y ,
highlight_symbol ,
list_area . width as usize ,
highlight_style ,
) ;
( x + 1 , Some ( highlight_style ) )
} else {
let ( x , _ ) = buf . set_stringn (
x ,
y ,
& blank_symbol ,
list_area . width as usize ,
highlight_style ,
) ;
( x + 1 , None )
}
} else {
( x , None )
} ;
match item {
Text ::Raw ( ref v ) = > {
buf . set_stringn ( x , y , v , list_area . width as usize , Style ::default ( ) ) ;
buf . set_stringn (
x ,
y ,
v ,
list_area . width as usize ,
style . unwrap_or ( self . style ) ,
) ;
}
Text ::Styled ( ref v , s ) = > {
buf . set_stringn ( x , y , v , list_area . width as usize , s ) ;
buf . set_stringn ( x , y , v , list_area . width as usize , s tyle. unwrap_or ( s ) ) ;
}
} ;
}
}
}
/// A widget to display several items among which one can be selected (optional)
///
/// # Examples
///
/// ```
/// # use tui::widgets::{Block, Borders, SelectableList};
/// # use tui::style::{Style, Color, Modifier};
/// SelectableList::default()
/// .block(Block::default().title("SelectableList").borders(Borders::ALL))
/// .items(&["Item 1", "Item 2", "Item 3"])
/// .select(Some(1))
/// .style(Style::default().fg(Color::White))
/// .highlight_style(Style::default().modifier(Modifier::ITALIC))
/// .highlight_symbol(">>");
/// ```
pub struct SelectableList < ' b > {
block : Option < Block < ' b > > ,
/// Items to be displayed
items : Vec < & ' b str > ,
/// Index of the one selected
selected : Option < usize > ,
/// Base style of the widget
style : Style ,
/// Style used to render selected item
highlight_style : Style ,
/// Symbol in front of the selected item (Shift all items to the right)
highlight_symbol : Option < & ' b str > ,
}
impl < ' b > Default for SelectableList < ' b > {
fn default ( ) -> SelectableList < ' b > {
SelectableList {
block : None ,
items : Vec ::new ( ) ,
selected : None ,
style : Default ::default ( ) ,
highlight_style : Default ::default ( ) ,
highlight_symbol : None ,
}
}
}
impl < ' b > SelectableList < ' b > {
pub fn block ( mut self , block : Block < ' b > ) -> SelectableList < ' b > {
self . block = Some ( block ) ;
self
}
pub fn items < I > ( mut self , items : & ' b [ I ] ) -> SelectableList < ' b >
where
I : AsRef < str > + ' b ,
{
self . items = items . iter ( ) . map ( AsRef ::as_ref ) . collect ::< Vec < & str > > ( ) ;
self
}
pub fn style ( mut self , style : Style ) -> SelectableList < ' b > {
self . style = style ;
self
}
pub fn highlight_symbol ( mut self , highlight_symbol : & ' b str ) -> SelectableList < ' b > {
self . highlight_symbol = Some ( highlight_symbol ) ;
self
}
pub fn highlight_style ( mut self , highlight_style : Style ) -> SelectableList < ' b > {
self . highlight_style = highlight_style ;
self
}
pub fn select ( mut self , index : Option < usize > ) -> SelectableList < ' b > {
self . selected = index ;
self
}
}
impl < ' b > Widget for SelectableList < ' b > {
fn draw ( & mut self , area : Rect , buf : & mut Buffer ) {
let list_area = match self . block {
Some ( ref mut b ) = > b . inner ( area ) ,
None = > area ,
} ;
let list_height = list_area . height as usize ;
// Use highlight_style only if something is selected
let ( selected , highlight_style ) = match self . selected {
Some ( i ) = > ( Some ( i ) , self . highlight_style ) ,
None = > ( None , self . style ) ,
} ;
let highlight_symbol = self . highlight_symbol . unwrap_or ( "" ) ;
let blank_symbol = iter ::repeat ( " " )
. take ( highlight_symbol . width ( ) )
. collect ::< String > ( ) ;
// Make sure the list show the selected item
let offset = if let Some ( selected ) = selected {
if selected > = list_height {
selected - list_height + 1
} else {
0
}
} else {
0
} ;
// Render items
let items = self
. items
. iter ( )
. enumerate ( )
. map ( | ( i , & item ) | {
if let Some ( s ) = selected {
if i = = s {
Text ::styled ( format! ( "{} {}" , highlight_symbol , item ) , highlight_style )
} else {
Text ::styled ( format! ( "{} {}" , blank_symbol , item ) , self . style )
}
} else {
Text ::styled ( item , self . style )
}
} )
. skip ( offset as usize ) ;
List ::new ( items )
. block ( self . block . unwrap_or_default ( ) )
. style ( self . style )
. draw ( area , buf ) ;
impl < ' b , L > Widget for List < ' b , L >
where
L : Iterator < Item = Text < ' b > > ,
{
fn render ( self , area : Rect , buf : & mut Buffer ) {
let mut state = ListState ::default ( ) ;
StatefulWidget ::render ( self , area , buf , & mut state ) ;
}
}