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