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 _ => (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
115pub struct SelectableList<'b> {
133 block: Option<Block<'b>>,
134 items: Vec<&'b str>,
136 selected: Option<usize>,
138 style: Style,
140 highlight_style: Style,
142 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 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 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 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}