use std::{collections::BTreeSet, ops::Deref, rc::Rc};
use crate::prelude::*;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum Selectable {
#[default]
None,
Single,
Multi,
}
impl_res_simple!(Selectable);
pub enum ListEvent {
Select(usize),
SelectFocused,
FocusNext,
FocusPrev,
ClearSelection,
}
#[derive(Lens)]
pub struct List {
list_len: usize,
selected: BTreeSet<usize>,
selectable: Selectable,
focused: Option<usize>,
focus_visible: bool,
selection_follows_focus: bool,
horizontal: bool,
on_select: Option<Box<dyn Fn(&mut EventContext, usize)>>,
}
impl List {
pub fn new<L: Lens, T: 'static>(
cx: &mut Context,
list: L,
item_content: impl 'static + Fn(&mut Context, usize, MapRef<L, T>),
) -> Handle<Self>
where
L::Target: Deref<Target = [T]> + Data,
{
Self::new_generic(
cx,
list,
|list| list.len(),
|list, index| &list[index],
|_| true,
item_content,
)
}
pub fn new_filtered<L: Lens, T: 'static>(
cx: &mut Context,
list: L,
filter: impl 'static + Clone + FnMut(&&T) -> bool,
item_content: impl 'static + Fn(&mut Context, usize, MapRef<L, T>),
) -> Handle<Self>
where
L::Target: Deref<Target = [T]> + Data,
{
let f = filter.clone();
Self::new_generic(
cx,
list,
move |list| list.iter().filter(filter.clone()).count(),
move |list, index| &list[index],
f,
item_content,
)
}
pub fn new_generic<L: Lens, T: 'static>(
cx: &mut Context,
list: L,
list_len: impl 'static + Fn(&L::Target) -> usize,
list_index: impl 'static + Clone + Fn(&L::Target, usize) -> &T,
filter: impl 'static + Clone + FnMut(&&T) -> bool,
item_content: impl 'static + Fn(&mut Context, usize, MapRef<L, T>),
) -> Handle<Self>
where
L::Target: Deref<Target = [T]> + Data,
{
let content = Rc::new(item_content);
let num_items = list.map(list_len);
Self {
list_len: num_items.get(cx),
selected: BTreeSet::default(),
selectable: Selectable::None,
focused: None,
focus_visible: false,
selection_follows_focus: false,
horizontal: false,
on_select: None,
}
.build(cx, move |cx| {
Keymap::from(vec![
(
KeyChord::new(Modifiers::empty(), Code::ArrowDown),
KeymapEntry::new("Focus Next", |cx| cx.emit(ListEvent::FocusNext)),
),
(
KeyChord::new(Modifiers::empty(), Code::ArrowUp),
KeymapEntry::new("Focus Previous", |cx| cx.emit(ListEvent::FocusPrev)),
),
(
KeyChord::new(Modifiers::empty(), Code::Space),
KeymapEntry::new("Select Focused", |cx| cx.emit(ListEvent::SelectFocused)),
),
(
KeyChord::new(Modifiers::empty(), Code::Enter),
KeymapEntry::new("Select Focused", |cx| cx.emit(ListEvent::SelectFocused)),
),
])
.build(cx);
Binding::new(cx, List::horizontal, |cx, horizontal| {
if horizontal.get(cx) {
cx.emit(KeymapEvent::RemoveAction(
KeyChord::new(Modifiers::empty(), Code::ArrowDown),
"Focus Next",
));
cx.emit(KeymapEvent::RemoveAction(
KeyChord::new(Modifiers::empty(), Code::ArrowUp),
"Focus Previous",
));
cx.emit(KeymapEvent::InsertAction(
KeyChord::new(Modifiers::empty(), Code::ArrowRight),
KeymapEntry::new("Focus Next", |cx| cx.emit(ListEvent::FocusNext)),
));
cx.emit(KeymapEvent::InsertAction(
KeyChord::new(Modifiers::empty(), Code::ArrowLeft),
KeymapEntry::new("Focus Previous", |cx| cx.emit(ListEvent::FocusPrev)),
));
}
});
ScrollView::new(cx, move |cx| {
Binding::new(cx, num_items, move |cx, _| {
let mut f = filter.clone();
let ll = list
.get(cx)
.iter()
.enumerate()
.filter(|(_, v)| f(v))
.map(|(idx, _)| idx)
.collect::<Vec<_>>();
for index in ll.into_iter() {
let ll = list_index.clone();
let item = list.map_ref(move |list| ll(list, index));
let content = content.clone();
ListItem::new(cx, index, item, move |cx, index, item| {
content(cx, index, item);
});
}
});
});
})
.toggle_class("selectable", List::selectable.map(|s| *s != Selectable::None))
.toggle_class("horizontal", List::horizontal)
.navigable(true)
.role(Role::List)
}
}
impl View for List {
fn element(&self) -> Option<&'static str> {
Some("list")
}
fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
event.take(|list_event, _| match list_event {
ListEvent::Select(index) => {
cx.focus();
match self.selectable {
Selectable::Single => {
if self.selected.contains(&index) {
self.selected.clear();
self.focused = None;
} else {
self.selected.clear();
self.selected.insert(index);
self.focused = Some(index);
self.focus_visible = false;
if let Some(on_select) = &self.on_select {
on_select(cx, index);
}
}
}
Selectable::Multi => {
if self.selected.contains(&index) {
self.selected.remove(&index);
self.focused = None;
} else {
self.selected.insert(index);
self.focused = Some(index);
self.focus_visible = false;
if let Some(on_select) = &self.on_select {
on_select(cx, index);
}
}
}
Selectable::None => {}
}
}
ListEvent::SelectFocused => {
if let Some(focused) = &self.focused {
cx.emit(ListEvent::Select(*focused))
}
}
ListEvent::ClearSelection => {
self.selected.clear();
}
ListEvent::FocusNext => {
if let Some(focused) = &mut self.focused {
*focused = focused.saturating_add(1);
if *focused >= self.list_len {
*focused = 0;
}
} else {
self.focused = Some(0);
}
self.focus_visible = true;
if self.selection_follows_focus {
cx.emit(ListEvent::SelectFocused);
}
}
ListEvent::FocusPrev => {
if let Some(focused) = &mut self.focused {
if *focused == 0 {
*focused = self.list_len;
}
*focused = focused.saturating_sub(1);
} else {
self.focused = Some(self.list_len.saturating_sub(1));
}
self.focus_visible = true;
if self.selection_follows_focus {
cx.emit(ListEvent::SelectFocused);
}
}
})
}
}
impl Handle<'_, List> {
pub fn selected<S: Lens>(self, selected: S) -> Self
where
S::Target: Deref<Target = [usize]> + Data,
{
self.bind(selected, |handle, s| {
let ss = s.get(&handle).deref().to_vec();
handle.modify(|list| {
for idx in ss {
list.selected.insert(idx);
list.focused = Some(idx);
}
});
})
}
pub fn on_select<F>(self, callback: F) -> Self
where
F: 'static + Fn(&mut EventContext, usize),
{
self.modify(|list: &mut List| list.on_select = Some(Box::new(callback)))
}
pub fn selectable<U: Into<Selectable>>(self, selectable: impl Res<U>) -> Self {
self.bind(selectable, |handle, selectable| {
let s = selectable.get(&handle).into();
handle.modify(|list: &mut List| list.selectable = s);
})
}
pub fn selection_follows_focus<U: Into<bool>>(self, flag: impl Res<U>) -> Self {
self.bind(flag, |handle, selection_follows_focus| {
let s = selection_follows_focus.get(&handle).into();
handle.modify(|list: &mut List| list.selection_follows_focus = s);
})
}
pub fn horizontal<U: Into<bool>>(self, flag: impl Res<U>) -> Self {
self.bind(flag, |handle, horizontal| {
let s = horizontal.get(&handle).into();
handle.modify(|list: &mut List| list.horizontal = s);
})
}
}
pub struct ListItem {}
impl ListItem {
pub fn new<L: Lens, T: 'static>(
cx: &mut Context,
index: usize,
item: MapRef<L, T>,
item_content: impl 'static + Fn(&mut Context, usize, MapRef<L, T>),
) -> Handle<Self> {
Self {}
.build(cx, move |cx| {
item_content(cx, index, item);
})
.role(Role::ListItem)
.checked(List::selected.map(move |selected| selected.contains(&index)))
.focused_with_visibility(
List::focused.map(move |f| *f == Some(index)),
List::focus_visible,
)
.on_press(move |cx| cx.emit(ListEvent::Select(index)))
}
}
impl View for ListItem {
fn element(&self) -> Option<&'static str> {
Some("list-item")
}
}