revue/widget/data/
list.rs1use crate::render::Cell;
4use crate::style::Color;
5use crate::utils::Selection;
6use crate::widget::traits::{RenderContext, View, WidgetProps};
7use std::fmt::Display;
8
9pub struct List<T> {
11 items: Vec<T>,
12 selection: Selection,
13 highlight_fg: Option<Color>,
14 highlight_bg: Option<Color>,
15 props: WidgetProps,
16}
17
18impl<T> List<T> {
19 pub fn new(items: Vec<T>) -> Self {
21 let len = items.len();
22 Self {
23 items,
24 selection: Selection::new(len),
25 highlight_fg: None,
26 highlight_bg: Some(Color::BLUE),
27 props: WidgetProps::new(),
28 }
29 }
30
31 pub fn selected(mut self, idx: usize) -> Self {
33 self.selection.set(idx);
34 self
35 }
36
37 pub fn highlight_fg(mut self, color: Color) -> Self {
39 self.highlight_fg = Some(color);
40 self
41 }
42
43 pub fn highlight_bg(mut self, color: Color) -> Self {
45 self.highlight_bg = Some(color);
46 self
47 }
48
49 pub fn items(&self) -> &[T] {
51 &self.items
52 }
53
54 pub fn selected_index(&self) -> usize {
56 self.selection.index
57 }
58
59 pub fn len(&self) -> usize {
61 self.items.len()
62 }
63
64 pub fn is_empty(&self) -> bool {
66 self.items.is_empty()
67 }
68
69 pub fn select_next(&mut self) {
71 self.selection.next();
72 }
73
74 pub fn select_prev(&mut self) {
76 self.selection.prev();
77 }
78
79 pub fn select_first(&mut self) {
81 self.selection.set(0);
82 }
83
84 pub fn select_last(&mut self) {
86 if !self.items.is_empty() {
87 self.selection.set(self.items.len() - 1);
88 }
89 }
90
91 pub fn handle_key(&mut self, key: &crate::event::Key) -> bool {
93 use crate::event::Key;
94 match key {
95 Key::Up | Key::Char('k') => {
96 self.select_prev();
97 true
98 }
99 Key::Down | Key::Char('j') => {
100 self.select_next();
101 true
102 }
103 Key::Home => {
104 self.select_first();
105 true
106 }
107 Key::End => {
108 self.select_last();
109 true
110 }
111 _ => false,
112 }
113 }
114}
115
116impl<T: Display> View for List<T> {
117 crate::impl_view_meta!("List");
118 fn render(&self, ctx: &mut RenderContext) {
119 let area = ctx.area;
120 if area.width == 0 || area.height == 0 {
121 return;
122 }
123
124 for (i, item) in self.items.iter().enumerate() {
126 if i as u16 >= area.height {
127 break;
128 }
129
130 let y = i as u16;
131 let is_selected = self.selection.is_selected(i);
132
133 let text = item.to_string();
134 let mut x = 0u16;
135
136 for ch in text.chars() {
137 if x >= area.width {
138 break;
139 }
140
141 let mut cell = Cell::new(ch);
142 if is_selected {
143 cell.fg = self.highlight_fg;
144 cell.bg = self.highlight_bg;
145 }
146
147 ctx.set(x, y, cell);
148
149 let char_width = crate::utils::unicode::char_width(ch).max(1) as u16;
150 if char_width == 2 && x + 1 < area.width {
151 let mut cont = Cell::continuation();
152 if is_selected {
153 cont.bg = self.highlight_bg;
154 }
155 ctx.set(x + 1, y, cont);
156 }
157 x += char_width;
158 }
159
160 if is_selected {
162 while x < area.width {
163 let mut cell = Cell::new(' ');
164 cell.bg = self.highlight_bg;
165 ctx.set(x, y, cell);
166 x += 1;
167 }
168 }
169 }
170 }
171}
172
173impl<T: Display> crate::widget::StyledView for List<T> {
176 fn set_id(&mut self, id: impl Into<String>) {
177 self.props.id = Some(id.into());
178 }
179
180 fn add_class(&mut self, class: impl Into<String>) {
181 let class_str = class.into();
182 if !self.props.classes.iter().any(|c| c == &class_str) {
183 self.props.classes.push(class_str);
184 }
185 }
186
187 fn remove_class(&mut self, class: &str) {
188 self.props.classes.retain(|c| c != class);
189 }
190
191 fn toggle_class(&mut self, class: &str) {
192 if self.props.classes.iter().any(|c| c == class) {
193 self.props.classes.retain(|c| c != class);
194 } else {
195 self.props.classes.push(class.to_string());
196 }
197 }
198
199 fn has_class(&self, class: &str) -> bool {
200 self.props.classes.iter().any(|c| c == class)
201 }
202}
203
204pub fn list<T>(items: Vec<T>) -> List<T> {
206 List::new(items)
207}
208
209