tui_temp_fork/widgets/
list.rs

1use std::convert::AsRef;
2use std::iter::{self, Iterator};
3
4use unicode_width::UnicodeWidthStr;
5
6use crate::buffer::Buffer;
7use crate::layout::{Corner, Rect};
8use crate::style::Style;
9use crate::widgets::{Block, Text, Widget};
10
11pub struct List<'b, L>
12where
13    L: Iterator<Item = Text<'b>>,
14{
15    block: Option<Block<'b>>,
16    items: L,
17    style: Style,
18    start_corner: Corner,
19}
20
21impl<'b, L> Default for List<'b, L>
22where
23    L: Iterator<Item = Text<'b>> + Default,
24{
25    fn default() -> List<'b, L> {
26        List {
27            block: None,
28            items: L::default(),
29            style: Default::default(),
30            start_corner: Corner::TopLeft,
31        }
32    }
33}
34
35impl<'b, L> List<'b, L>
36where
37    L: Iterator<Item = Text<'b>>,
38{
39    pub fn new(items: L) -> List<'b, L> {
40        List {
41            block: None,
42            items,
43            style: Default::default(),
44            start_corner: Corner::TopLeft,
45        }
46    }
47
48    pub fn block(mut self, block: Block<'b>) -> List<'b, L> {
49        self.block = Some(block);
50        self
51    }
52
53    pub fn items<I>(mut self, items: I) -> List<'b, L>
54    where
55        I: IntoIterator<Item = Text<'b>, IntoIter = L>,
56    {
57        self.items = items.into_iter();
58        self
59    }
60
61    pub fn style(mut self, style: Style) -> List<'b, L> {
62        self.style = style;
63        self
64    }
65
66    pub fn start_corner(mut self, corner: Corner) -> List<'b, L> {
67        self.start_corner = corner;
68        self
69    }
70}
71
72impl<'b, L> Widget for List<'b, L>
73where
74    L: Iterator<Item = Text<'b>>,
75{
76    fn draw(&mut self, area: Rect, buf: &mut Buffer) {
77        let list_area = match self.block {
78            Some(ref mut b) => {
79                b.draw(area, buf);
80                b.inner(area)
81            }
82            None => area,
83        };
84
85        if list_area.width < 1 || list_area.height < 1 {
86            return;
87        }
88
89        self.background(list_area, buf, self.style.bg);
90
91        for (i, item) in self
92            .items
93            .by_ref()
94            .enumerate()
95            .take(list_area.height as usize)
96        {
97            let (x, y) = match self.start_corner {
98                Corner::TopLeft => (list_area.left(), list_area.top() + i as u16),
99                Corner::BottomLeft => (list_area.left(), list_area.bottom() - (i + 1) as u16),
100                // Not supported
101                _ => (list_area.left(), list_area.top() + i as u16),
102            };
103            match item {
104                Text::Raw(ref v) => {
105                    buf.set_stringn(x, y, v, list_area.width as usize, Style::default());
106                }
107                Text::Styled(ref v, s) => {
108                    buf.set_stringn(x, y, v, list_area.width as usize, s);
109                }
110            };
111        }
112    }
113}
114
115/// A widget to display several items among which one can be selected (optional)
116///
117/// # Examples
118///
119/// ```
120/// # use tui_temp_fork::widgets::{Block, Borders, SelectableList};
121/// # use tui_temp_fork::style::{Style, Color, Modifier};
122/// # fn main() {
123/// SelectableList::default()
124///     .block(Block::default().title("SelectableList").borders(Borders::ALL))
125///     .items(&["Item 1", "Item 2", "Item 3"])
126///     .select(Some(1))
127///     .style(Style::default().fg(Color::White))
128///     .highlight_style(Style::default().modifier(Modifier::ITALIC))
129///     .highlight_symbol(">>");
130/// # }
131/// ```
132pub struct SelectableList<'b> {
133    block: Option<Block<'b>>,
134    /// Items to be displayed
135    items: Vec<&'b str>,
136    /// Index of the one selected
137    selected: Option<usize>,
138    /// Base style of the widget
139    style: Style,
140    /// Style used to render selected item
141    highlight_style: Style,
142    /// Symbol in front of the selected item (Shift all items to the right)
143    highlight_symbol: Option<&'b str>,
144}
145
146impl<'b> Default for SelectableList<'b> {
147    fn default() -> SelectableList<'b> {
148        SelectableList {
149            block: None,
150            items: Vec::new(),
151            selected: None,
152            style: Default::default(),
153            highlight_style: Default::default(),
154            highlight_symbol: None,
155        }
156    }
157}
158
159impl<'b> SelectableList<'b> {
160    pub fn block(mut self, block: Block<'b>) -> SelectableList<'b> {
161        self.block = Some(block);
162        self
163    }
164
165    pub fn items<I>(mut self, items: &'b [I]) -> SelectableList<'b>
166    where
167        I: AsRef<str> + 'b,
168    {
169        self.items = items.iter().map(AsRef::as_ref).collect::<Vec<&str>>();
170        self
171    }
172
173    pub fn style(mut self, style: Style) -> SelectableList<'b> {
174        self.style = style;
175        self
176    }
177
178    pub fn highlight_symbol(mut self, highlight_symbol: &'b str) -> SelectableList<'b> {
179        self.highlight_symbol = Some(highlight_symbol);
180        self
181    }
182
183    pub fn highlight_style(mut self, highlight_style: Style) -> SelectableList<'b> {
184        self.highlight_style = highlight_style;
185        self
186    }
187
188    pub fn select(mut self, index: Option<usize>) -> SelectableList<'b> {
189        self.selected = index;
190        self
191    }
192}
193
194impl<'b> Widget for SelectableList<'b> {
195    fn draw(&mut self, area: Rect, buf: &mut Buffer) {
196        let list_area = match self.block {
197            Some(ref mut b) => b.inner(area),
198            None => area,
199        };
200
201        let list_height = list_area.height as usize;
202
203        // Use highlight_style only if something is selected
204        let (selected, highlight_style) = match self.selected {
205            Some(i) => (Some(i), self.highlight_style),
206            None => (None, self.style),
207        };
208        let highlight_symbol = self.highlight_symbol.unwrap_or("");
209        let blank_symbol = iter::repeat(" ")
210            .take(highlight_symbol.width())
211            .collect::<String>();
212        // Make sure the list show the selected item
213        let offset = if let Some(selected) = selected {
214            if selected >= list_height {
215                selected - list_height + 1
216            } else {
217                0
218            }
219        } else {
220            0
221        };
222
223        // Render items
224        let items = self
225            .items
226            .iter()
227            .enumerate()
228            .map(|(i, &item)| {
229                if let Some(s) = selected {
230                    if i == s {
231                        Text::styled(format!("{} {}", highlight_symbol, item), highlight_style)
232                    } else {
233                        Text::styled(format!("{} {}", blank_symbol, item), self.style)
234                    }
235                } else {
236                    Text::styled(item, self.style)
237                }
238            })
239            .skip(offset as usize);
240        List::new(items)
241            .block(self.block.unwrap_or_default())
242            .style(self.style)
243            .draw(area, buf);
244    }
245}