vizia_core/views/
list.rs

1use std::{collections::BTreeSet, ops::Deref, rc::Rc};
2
3use crate::prelude::*;
4
5/// Represents how items can be selected in a list.
6#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
7pub enum Selectable {
8    #[default]
9    /// Items in the list cannot be selected.
10    None,
11    /// A single item in the list can be selected.
12    Single,
13    /// Multiple items in the list can be selected simultaneously.
14    Multi,
15}
16
17impl_res_simple!(Selectable);
18
19/// Events used by the [List] view
20pub enum ListEvent {
21    /// Selects a list item with the given index.
22    Select(usize),
23    /// Selects the focused list item.
24    SelectFocused,
25    ///  Moves the focus to the next item in the list.
26    FocusNext,
27    ///  Moves the focus to the previous item in the list.
28    FocusPrev,
29    /// Deselects all items from the list
30    ClearSelection,
31}
32
33/// A view for creating a list of items from a binding to an iteratable list.
34#[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    /// Creates a new [List] view.
48    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    /// Creates a new [List] view with a provided filter closure.
67    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    /// Creates a new [List] view with a binding to the given lens and a template for constructing the list items.
88    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                // Bind to the list data
158                Binding::new(cx, num_items, move |cx, _| {
159                    // If the number of list items is different to the number of children of the ListView
160                    // then remove and rebuild all the children
161
162                    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    /// Sets the  selected items of the list. Takes a lens to a list of indices.
283    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    /// Sets the callback triggered when a [ListItem] is selected.
299    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    /// Set the selectable state of the [List].
307    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    /// Sets whether the selection should follow the focus.
315    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    // todo: replace with orientation
323    /// Sets the orientation of the list.
324    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
332/// A view which represents a selectable item within a list.
333pub struct ListItem {}
334
335impl ListItem {
336    /// Create a new [ListItem] view.
337    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            //.toggle_class("focused", List::focused.map(move |focused| *focused == Some(index)))
350            .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}