use std::iter::{self, Iterator}; use unicode_width::UnicodeWidthStr; use crate::buffer::Buffer; use crate::layout::{Corner, Rect}; use crate::style::Style; use crate::widgets::{Block, StatefulWidget, Text, Widget}; #[derive(Debug, Clone)] pub struct ListState { offset: usize, selected: Option, } impl Default for ListState { fn default() -> ListState { ListState { offset: 0, selected: None, } } } impl ListState { pub fn selected(&self) -> Option { self.selected } pub fn select(&mut self, index: Option) { 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}; /// 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(">>"); /// ``` #[derive(Debug, Clone)] pub struct List<'b, L> where L: Iterator>, { block: Option>, items: L, 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> where L: Iterator> + Default, { fn default() -> List<'b, L> { List { block: None, items: L::default(), style: Default::default(), start_corner: Corner::TopLeft, highlight_style: Style::default(), highlight_symbol: None, } } } impl<'b, L> List<'b, L> where L: Iterator>, { pub fn new(items: L) -> List<'b, L> { List { block: None, items, style: Default::default(), start_corner: Corner::TopLeft, highlight_style: Style::default(), highlight_symbol: None, } } pub fn block(mut self, block: Block<'b>) -> List<'b, L> { self.block = Some(block); self } pub fn items(mut self, items: I) -> List<'b, L> where I: IntoIterator, IntoIter = L>, { self.items = items.into_iter(); self } pub fn style(mut self, style: Style) -> List<'b, L> { self.style = style; 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> StatefulWidget for List<'b, L> where L: Iterator>, { 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.render(area, buf); b.inner(area) } None => area, }; if list_area.width < 1 || list_area.height < 1 { return; } 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::(); // 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 .skip(state.offset) .enumerate() .take(list_area.height as usize) { let (x, y) = match self.start_corner { Corner::TopLeft => (list_area.left(), list_area.top() + i as u16), Corner::BottomLeft => (list_area.left(), list_area.bottom() - (i + 1) as u16), // Not supported _ => (list_area.left(), list_area.top() + i as u16), }; let (elem_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, Some(highlight_style)) } else { let (x, _) = buf.set_stringn(x, y, &blank_symbol, list_area.width as usize, self.style); (x, None) } } else { (x, None) }; let max_element_width = (list_area.width - (elem_x - x)) as usize; match item { Text::Raw(ref v) => { buf.set_stringn(elem_x, y, v, max_element_width, style.unwrap_or(self.style)); } Text::Styled(ref v, s) => { buf.set_stringn(elem_x, y, v, max_element_width, style.unwrap_or(s)); } }; } } } impl<'b, L> Widget for List<'b, L> where L: Iterator>, { fn render(self, area: Rect, buf: &mut Buffer) { let mut state = ListState::default(); StatefulWidget::render(self, area, buf, &mut state); } }