1use floem_reactive::create_effect;
2use taffy::FlexDirection;
3use ui_events::keyboard::{Key, NamedKey};
4
5use crate::event::{Event, EventListener, EventPropagation};
6use crate::{ViewId, prelude::*};
7
8use std::hash::{DefaultHasher, Hash, Hasher};
9use std::ops::{Deref, DerefMut};
10
11pub struct VirtualList<T: 'static> {
12 stack: VirtualStack<(usize, T)>,
13 pub selection: RwSignal<Option<usize>>,
14}
15
16impl<T> VirtualList<T> {
17 pub fn new<DF, I>(data_fn: DF) -> Self
19 where
20 DF: Fn() -> I + 'static,
21 I: VirtualVector<T>,
22 T: Hash + Eq + IntoView + 'static,
23 {
24 Self::full(
25 data_fn,
26 |item| {
27 let mut hasher = DefaultHasher::new();
28 item.hash(&mut hasher);
29 hasher.finish()
30 },
31 |_index, item| item.into_view(),
32 )
33 }
34
35 pub fn with_view<DF, I, V>(data_fn: DF, view_fn: impl Fn(T) -> V + 'static) -> Self
37 where
38 DF: Fn() -> I + 'static,
39 I: VirtualVector<T>,
40 T: Hash + Eq + 'static,
41 V: IntoView,
42 {
43 Self::full(
44 data_fn,
45 |item| {
46 let mut hasher = DefaultHasher::new();
47 item.hash(&mut hasher);
48 hasher.finish()
49 },
50 move |_index, item| view_fn(item).into_view(),
51 )
52 }
53
54 pub fn with_key<DF, I, K>(data_fn: DF, key_fn: impl Fn(&T) -> K + 'static) -> Self
56 where
57 DF: Fn() -> I + 'static,
58 I: VirtualVector<T>,
59 T: IntoView + 'static,
60 K: Hash + Eq + 'static,
61 {
62 Self::full(data_fn, key_fn, |_index, item| item.into_view())
63 }
64
65 pub fn full<DF, I, KF, K, VF, V>(data_fn: DF, key_fn: KF, view_fn: VF) -> Self
66 where
67 DF: Fn() -> I + 'static,
68 I: VirtualVector<T>,
69 KF: Fn(&T) -> K + 'static,
70 K: Eq + Hash + 'static,
71 VF: Fn(usize, T) -> V + 'static,
72 V: IntoView + 'static,
73 T: 'static,
74 {
75 virtual_list(data_fn, key_fn, view_fn)
76 }
77}
78
79impl<T: 'static> Deref for VirtualList<T> {
80 type Target = VirtualStack<(usize, T)>;
81 fn deref(&self) -> &VirtualStack<(usize, T)> {
82 &self.stack
83 }
84}
85
86impl<T: 'static> DerefMut for VirtualList<T> {
87 fn deref_mut(&mut self) -> &mut VirtualStack<(usize, T)> {
88 &mut self.stack
89 }
90}
91
92impl<T: 'static> VirtualList<T> {
93 pub fn selection(&self) -> RwSignal<Option<usize>> {
94 self.selection
95 }
96
97 pub fn on_select(self, on_select: impl Fn(Option<usize>) + 'static) -> Self {
134 create_effect(move |_| {
135 let selection = self.selection.get();
136 on_select(selection);
137 });
138 self
139 }
140}
141
142pub fn virtual_list<T, DF, I, KF, K, VF, V>(data_fn: DF, key_fn: KF, view_fn: VF) -> VirtualList<T>
145where
146 DF: Fn() -> I + 'static,
147 I: VirtualVector<T>,
148 KF: Fn(&T) -> K + 'static,
149 K: Eq + Hash + 'static,
150 VF: Fn(usize, T) -> V + 'static,
151 V: IntoView + 'static,
152{
153 let selection = RwSignal::new(None::<usize>);
154 let length = RwSignal::new(0);
155
156 let stack = virtual_stack(
157 move || {
158 let vector = data_fn().enumerate();
159 length.set(vector.total_len());
160 vector
161 },
162 move |(_i, d)| key_fn(d),
163 move |(index, e)| {
164 let child = view_fn(index, e).class(ListItemClass);
165 let child_id = child.id();
166 child.on_click_cont(move |_| {
167 if selection.get_untracked() != Some(index) {
168 selection.set(Some(index));
169 child_id.scroll_to(None);
170 let Some(parent) = child_id.parent() else {
171 return;
172 };
173 parent.update_state(index);
174 parent.request_style_recursive();
175 }
176 })
177 },
178 )
179 .style(|s| s.size_full());
180
181 let stack_id = stack.id();
182
183 create_effect(move |_| {
184 if let Some(idx) = selection.get() {
185 stack_id.update_state(idx);
186 }
187 });
188
189 let direction = stack.direction;
190
191 let stack = stack
192 .class(ListClass)
193 .on_event(EventListener::KeyDown, move |e| {
194 if let Event::Key(key_event) = e {
195 match key_event.key {
196 Key::Named(NamedKey::Home) => {
197 if length.get_untracked() > 0 {
198 selection.set(Some(0));
199 stack_id.update_state(0_usize); }
201 EventPropagation::Stop
202 }
203 Key::Named(NamedKey::End) => {
204 let len = length.get_untracked();
205 if len > 0 {
206 selection.set(Some(len - 1));
207 stack_id.update_state(len - 1);
208 }
209 EventPropagation::Stop
210 }
211 Key::Named(
212 named_key @ (NamedKey::ArrowUp
213 | NamedKey::ArrowDown
214 | NamedKey::ArrowLeft
215 | NamedKey::ArrowRight),
216 ) => handle_arrow_key(
217 selection,
218 length.get_untracked(),
219 direction.get_untracked(),
220 stack_id,
221 named_key,
222 ),
223 _ => EventPropagation::Continue,
224 }
225 } else {
226 EventPropagation::Continue
227 }
228 });
229 VirtualList { stack, selection }
230}
231
232fn handle_arrow_key(
233 selection: RwSignal<Option<usize>>,
234 len: usize,
235 direction: FlexDirection,
236 stack_id: ViewId,
237 key: NamedKey,
238) -> EventPropagation {
239 let current = selection.get();
240
241 let should_move_forward = matches!(
243 (direction, key),
244 (FlexDirection::Row, NamedKey::ArrowRight)
245 | (FlexDirection::RowReverse, NamedKey::ArrowLeft)
246 | (FlexDirection::Column, NamedKey::ArrowDown)
247 | (FlexDirection::ColumnReverse, NamedKey::ArrowUp)
248 );
249
250 let should_move_backward = matches!(
251 (direction, key),
252 (FlexDirection::Row, NamedKey::ArrowLeft)
253 | (FlexDirection::RowReverse, NamedKey::ArrowRight)
254 | (FlexDirection::Column, NamedKey::ArrowUp)
255 | (FlexDirection::ColumnReverse, NamedKey::ArrowDown)
256 );
257
258 let is_cross_axis = matches!(
260 (direction, key),
261 (
262 FlexDirection::Row | FlexDirection::RowReverse,
263 NamedKey::ArrowUp | NamedKey::ArrowDown
264 ) | (
265 FlexDirection::Column | FlexDirection::ColumnReverse,
266 NamedKey::ArrowLeft | NamedKey::ArrowRight
267 )
268 );
269
270 if is_cross_axis {
271 return EventPropagation::Continue;
272 }
273
274 match current {
275 Some(i) => {
276 if should_move_backward && i > 0 {
277 selection.set(Some(i - 1));
278 stack_id.update_state(i - 1);
279 } else if should_move_forward && i < len - 1 {
280 selection.set(Some(i + 1));
281 stack_id.update_state(i + 1);
282 }
283 }
284 None => {
285 if len > 0 {
286 let res = if should_move_backward { len - 1 } else { 0 };
287 selection.set(Some(res));
288 stack_id.update_state(res);
289 }
290 }
291 }
292 EventPropagation::Stop
293}
294
295impl<T: 'static> IntoView for VirtualList<T> {
296 type V = VirtualStack<(usize, T)>;
297
298 fn into_view(self) -> Self::V {
299 self.stack
300 }
301}