1use std::{collections::BTreeSet, ops::Deref, rc::Rc};
2
3use crate::prelude::*;
4
5#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
7pub enum Selectable {
8 #[default]
9 None,
11 Single,
13 Multi,
15}
16
17impl_res_simple!(Selectable);
18
19pub enum ListEvent {
21 Select(usize),
23 SelectFocused,
25 FocusNext,
27 FocusPrev,
29 ClearSelection,
31}
32
33#[derive(Lens)]
35pub struct List {
36 list_len: usize,
37 selected: BTreeSet<usize>,
38 selectable: Selectable,
39 focused: Option<usize>,
40 focus_visible: bool,
41 selection_follows_focus: bool,
42 horizontal: bool,
43 on_select: Option<Box<dyn Fn(&mut EventContext, usize)>>,
44}
45
46impl List {
47 pub fn new<L: Lens, T: 'static>(
49 cx: &mut Context,
50 list: L,
51 item_content: impl 'static + Fn(&mut Context, usize, MapRef<L, T>),
52 ) -> Handle<Self>
53 where
54 L::Target: Deref<Target = [T]> + Data,
55 {
56 Self::new_generic(
57 cx,
58 list,
59 |list| list.len(),
60 |list, index| &list[index],
61 |_| true,
62 item_content,
63 )
64 }
65
66 pub fn new_filtered<L: Lens, T: 'static>(
68 cx: &mut Context,
69 list: L,
70 filter: impl 'static + Clone + FnMut(&&T) -> bool,
71 item_content: impl 'static + Fn(&mut Context, usize, MapRef<L, T>),
72 ) -> Handle<Self>
73 where
74 L::Target: Deref<Target = [T]> + Data,
75 {
76 let f = filter.clone();
77 Self::new_generic(
78 cx,
79 list,
80 move |list| list.iter().filter(filter.clone()).count(),
81 move |list, index| &list[index],
82 f,
83 item_content,
84 )
85 }
86
87 pub fn new_generic<L: Lens, T: 'static>(
89 cx: &mut Context,
90 list: L,
91 list_len: impl 'static + Fn(&L::Target) -> usize,
92 list_index: impl 'static + Clone + Fn(&L::Target, usize) -> &T,
93 filter: impl 'static + Clone + FnMut(&&T) -> bool,
94 item_content: impl 'static + Fn(&mut Context, usize, MapRef<L, T>),
95 ) -> Handle<Self>
96 where
97 L::Target: Deref<Target = [T]> + Data,
98 {
99 let content = Rc::new(item_content);
100 let num_items = list.map(list_len);
101 Self {
102 list_len: num_items.get(cx),
103 selected: BTreeSet::default(),
104 selectable: Selectable::None,
105 focused: None,
106 focus_visible: false,
107 selection_follows_focus: false,
108 horizontal: false,
109 on_select: None,
110 }
111 .build(cx, move |cx| {
112 Keymap::from(vec![
113 (
114 KeyChord::new(Modifiers::empty(), Code::ArrowDown),
115 KeymapEntry::new("Focus Next", |cx| cx.emit(ListEvent::FocusNext)),
116 ),
117 (
118 KeyChord::new(Modifiers::empty(), Code::ArrowUp),
119 KeymapEntry::new("Focus Previous", |cx| cx.emit(ListEvent::FocusPrev)),
120 ),
121 (
122 KeyChord::new(Modifiers::empty(), Code::Space),
123 KeymapEntry::new("Select Focused", |cx| cx.emit(ListEvent::SelectFocused)),
124 ),
125 (
126 KeyChord::new(Modifiers::empty(), Code::Enter),
127 KeymapEntry::new("Select Focused", |cx| cx.emit(ListEvent::SelectFocused)),
128 ),
129 ])
130 .build(cx);
131
132 Binding::new(cx, List::horizontal, |cx, horizontal| {
133 if horizontal.get(cx) {
134 cx.emit(KeymapEvent::RemoveAction(
135 KeyChord::new(Modifiers::empty(), Code::ArrowDown),
136 "Focus Next",
137 ));
138
139 cx.emit(KeymapEvent::RemoveAction(
140 KeyChord::new(Modifiers::empty(), Code::ArrowUp),
141 "Focus Previous",
142 ));
143
144 cx.emit(KeymapEvent::InsertAction(
145 KeyChord::new(Modifiers::empty(), Code::ArrowRight),
146 KeymapEntry::new("Focus Next", |cx| cx.emit(ListEvent::FocusNext)),
147 ));
148
149 cx.emit(KeymapEvent::InsertAction(
150 KeyChord::new(Modifiers::empty(), Code::ArrowLeft),
151 KeymapEntry::new("Focus Previous", |cx| cx.emit(ListEvent::FocusPrev)),
152 ));
153 }
154 });
155
156 ScrollView::new(cx, move |cx| {
157 Binding::new(cx, num_items, move |cx, _| {
159 let mut f = filter.clone();
163 let ll = list
164 .get(cx)
165 .iter()
166 .enumerate()
167 .filter(|(_, v)| f(v))
168 .map(|(idx, _)| idx)
169 .collect::<Vec<_>>();
170
171 for index in ll.into_iter() {
172 let ll = list_index.clone();
173 let item = list.map_ref(move |list| ll(list, index));
174 let content = content.clone();
175 ListItem::new(cx, index, item, move |cx, index, item| {
176 content(cx, index, item);
177 });
178 }
179 });
180 });
181 })
182 .toggle_class("selectable", List::selectable.map(|s| *s != Selectable::None))
183 .toggle_class("horizontal", List::horizontal)
184 .navigable(true)
185 .role(Role::List)
186 }
187}
188
189impl View for List {
190 fn element(&self) -> Option<&'static str> {
191 Some("list")
192 }
193
194 fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
195 event.take(|list_event, _| match list_event {
196 ListEvent::Select(index) => {
197 cx.focus();
198 match self.selectable {
199 Selectable::Single => {
200 if self.selected.contains(&index) {
201 self.selected.clear();
202 self.focused = None;
203 } else {
204 self.selected.clear();
205 self.selected.insert(index);
206 self.focused = Some(index);
207 self.focus_visible = false;
208 if let Some(on_select) = &self.on_select {
209 on_select(cx, index);
210 }
211 }
212 }
213
214 Selectable::Multi => {
215 if self.selected.contains(&index) {
216 self.selected.remove(&index);
217 self.focused = None;
218 } else {
219 self.selected.insert(index);
220 self.focused = Some(index);
221 self.focus_visible = false;
222 if let Some(on_select) = &self.on_select {
223 on_select(cx, index);
224 }
225 }
226 }
227
228 Selectable::None => {}
229 }
230 }
231
232 ListEvent::SelectFocused => {
233 if let Some(focused) = &self.focused {
234 cx.emit(ListEvent::Select(*focused))
235 }
236 }
237
238 ListEvent::ClearSelection => {
239 self.selected.clear();
240 }
241
242 ListEvent::FocusNext => {
243 if let Some(focused) = &mut self.focused {
244 *focused = focused.saturating_add(1);
245
246 if *focused >= self.list_len {
247 *focused = 0;
248 }
249 } else {
250 self.focused = Some(0);
251 }
252
253 self.focus_visible = true;
254
255 if self.selection_follows_focus {
256 cx.emit(ListEvent::SelectFocused);
257 }
258 }
259
260 ListEvent::FocusPrev => {
261 if let Some(focused) = &mut self.focused {
262 if *focused == 0 {
263 *focused = self.list_len;
264 }
265
266 *focused = focused.saturating_sub(1);
267 } else {
268 self.focused = Some(self.list_len.saturating_sub(1));
269 }
270
271 self.focus_visible = true;
272
273 if self.selection_follows_focus {
274 cx.emit(ListEvent::SelectFocused);
275 }
276 }
277 })
278 }
279}
280
281impl Handle<'_, List> {
282 pub fn selected<S: Lens>(self, selected: S) -> Self
284 where
285 S::Target: Deref<Target = [usize]> + Data,
286 {
287 self.bind(selected, |handle, s| {
288 let ss = s.get(&handle).deref().to_vec();
289 handle.modify(|list| {
290 for idx in ss {
291 list.selected.insert(idx);
292 list.focused = Some(idx);
293 }
294 });
295 })
296 }
297
298 pub fn on_select<F>(self, callback: F) -> Self
300 where
301 F: 'static + Fn(&mut EventContext, usize),
302 {
303 self.modify(|list: &mut List| list.on_select = Some(Box::new(callback)))
304 }
305
306 pub fn selectable<U: Into<Selectable>>(self, selectable: impl Res<U>) -> Self {
308 self.bind(selectable, |handle, selectable| {
309 let s = selectable.get(&handle).into();
310 handle.modify(|list: &mut List| list.selectable = s);
311 })
312 }
313
314 pub fn selection_follows_focus<U: Into<bool>>(self, flag: impl Res<U>) -> Self {
316 self.bind(flag, |handle, selection_follows_focus| {
317 let s = selection_follows_focus.get(&handle).into();
318 handle.modify(|list: &mut List| list.selection_follows_focus = s);
319 })
320 }
321
322 pub fn horizontal<U: Into<bool>>(self, flag: impl Res<U>) -> Self {
325 self.bind(flag, |handle, horizontal| {
326 let s = horizontal.get(&handle).into();
327 handle.modify(|list: &mut List| list.horizontal = s);
328 })
329 }
330}
331
332pub struct ListItem {}
334
335impl ListItem {
336 pub fn new<L: Lens, T: 'static>(
338 cx: &mut Context,
339 index: usize,
340 item: MapRef<L, T>,
341 item_content: impl 'static + Fn(&mut Context, usize, MapRef<L, T>),
342 ) -> Handle<Self> {
343 Self {}
344 .build(cx, move |cx| {
345 item_content(cx, index, item);
346 })
347 .role(Role::ListItem)
348 .checked(List::selected.map(move |selected| selected.contains(&index)))
349 .focused_with_visibility(
351 List::focused.map(move |f| *f == Some(index)),
352 List::focus_visible,
353 )
354 .on_press(move |cx| cx.emit(ListEvent::Select(index)))
355 }
356}
357
358impl View for ListItem {
359 fn element(&self) -> Option<&'static str> {
360 Some("list-item")
361 }
362}