1use std::iter::{self, Iterator};
2
3use unicode_width::UnicodeWidthStr;
4
5use crate::buffer::Buffer;
6use crate::layout::{Corner, Rect};
7use crate::style::Style;
8use crate::widgets::{Block, StatefulWidget, Text, Widget};
9
10#[derive(Copy, Clone, Debug)]
11pub struct ListState {
12 offset: usize,
13 selected: Option<usize>,
14}
15
16impl Default for ListState {
17 fn default() -> ListState {
18 ListState {
19 offset: 0,
20 selected: None,
21 }
22 }
23}
24
25impl ListState {
26 pub fn selected(&self) -> Option<usize> {
27 self.selected
28 }
29
30 pub fn select(&mut self, index: Option<usize>) {
31 self.selected = index;
32 if index.is_none() {
33 self.offset = 0;
34 }
35 }
36}
37
38pub struct List<'b, L>
53where
54 L: Iterator<Item = Text<'b>>,
55{
56 block: Option<Block<'b>>,
57 items: L,
58 start_corner: Corner,
59 style: Style,
61 highlight_style: Style,
63 highlight_symbol: Option<&'b str>,
65}
66
67impl<'b, L> Default for List<'b, L>
68where
69 L: Iterator<Item = Text<'b>> + Default,
70{
71 fn default() -> List<'b, L> {
72 List {
73 block: None,
74 items: L::default(),
75 style: Default::default(),
76 start_corner: Corner::TopLeft,
77 highlight_style: Style::default(),
78 highlight_symbol: None,
79 }
80 }
81}
82
83impl<'b, L> List<'b, L>
84where
85 L: Iterator<Item = Text<'b>>,
86{
87 pub fn new(items: L) -> List<'b, L> {
88 List {
89 block: None,
90 items,
91 style: Default::default(),
92 start_corner: Corner::TopLeft,
93 highlight_style: Style::default(),
94 highlight_symbol: None,
95 }
96 }
97
98 pub fn block(mut self, block: Block<'b>) -> List<'b, L> {
99 self.block = Some(block);
100 self
101 }
102
103 pub fn items<I>(mut self, items: I) -> List<'b, L>
104 where
105 I: IntoIterator<Item = Text<'b>, IntoIter = L>,
106 {
107 self.items = items.into_iter();
108 self
109 }
110
111 pub fn style(mut self, style: Style) -> List<'b, L> {
112 self.style = style;
113 self
114 }
115
116 pub fn highlight_symbol(mut self, highlight_symbol: &'b str) -> List<'b, L> {
117 self.highlight_symbol = Some(highlight_symbol);
118 self
119 }
120
121 pub fn highlight_style(mut self, highlight_style: Style) -> List<'b, L> {
122 self.highlight_style = highlight_style;
123 self
124 }
125
126 pub fn start_corner(mut self, corner: Corner) -> List<'b, L> {
127 self.start_corner = corner;
128 self
129 }
130}
131
132impl<'b, L> StatefulWidget for List<'b, L>
133where
134 L: Iterator<Item = Text<'b>>,
135{
136 type State = ListState;
137
138 fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
139 let list_area = match self.block {
140 Some(ref mut b) => {
141 b.render(area, buf);
142 b.inner(area)
143 }
144 None => area,
145 };
146
147 if list_area.width < 1 || list_area.height < 1 {
148 return;
149 }
150
151 let list_height = list_area.height as usize;
152
153 buf.set_background(list_area, self.style.bg);
154
155 let (selected, highlight_style) = match state.selected {
157 Some(i) => (Some(i), self.highlight_style),
158 None => (None, self.style),
159 };
160 let highlight_symbol = self.highlight_symbol.unwrap_or("");
161 let blank_symbol = iter::repeat(" ")
162 .take(highlight_symbol.width())
163 .collect::<String>();
164
165 state.offset = if let Some(selected) = selected {
167 if selected >= list_height + state.offset - 1 {
168 selected + 1 - list_height
169 } else if selected < state.offset {
170 selected
171 } else {
172 state.offset
173 }
174 } else {
175 0
176 };
177
178 for (i, item) in self
179 .items
180 .skip(state.offset)
181 .enumerate()
182 .take(list_area.height as usize)
183 {
184 let (x, y) = match self.start_corner {
185 Corner::TopLeft => (list_area.left(), list_area.top() + i as u16),
186 Corner::BottomLeft => (list_area.left(), list_area.bottom() - (i + 1) as u16),
187 _ => (list_area.left(), list_area.top() + i as u16),
189 };
190 let (x, style) = if let Some(s) = selected {
191 if s == i + state.offset {
192 let (x, _) = buf.set_stringn(
193 x,
194 y,
195 highlight_symbol,
196 list_area.width as usize,
197 highlight_style,
198 );
199 (x + 1, Some(highlight_style))
200 } else {
201 let (x, _) = buf.set_stringn(
202 x,
203 y,
204 &blank_symbol,
205 list_area.width as usize,
206 highlight_style,
207 );
208 (x + 1, None)
209 }
210 } else {
211 (x, None)
212 };
213 match item {
214 Text::Raw(ref v) => {
215 buf.set_stringn(
216 x,
217 y,
218 v,
219 list_area.width as usize,
220 style.unwrap_or(self.style),
221 );
222 }
223 Text::Styled(ref v, s) => {
224 buf.set_stringn(x, y, v, list_area.width as usize, style.unwrap_or(s));
225 }
226 };
227 }
228 }
229}
230
231impl<'b, L> Widget for List<'b, L>
232where
233 L: Iterator<Item = Text<'b>>,
234{
235 fn render(self, area: Rect, buf: &mut Buffer) {
236 let mut state = ListState::default();
237 StatefulWidget::render(self, area, buf, &mut state);
238 }
239}