1use std::{marker::PhantomData, ops::Deref, rc::Rc, sync::Arc};
2
3use crate::prelude::*;
4
5use super::{
6 TableColumn, TableSortCycle, TableSortDirection, TableSortState, table::next_sort_direction,
7 table::sort_direction_for_column,
8};
9
10pub struct VirtualTable<T, V, Id, H, K = String>
14where
15 V: Deref<Target = [T]> + Clone + 'static,
16 T: PartialEq + Clone + 'static,
17 Id: PartialEq + Clone + 'static,
18 H: View,
19 K: Clone + PartialEq + Send + Sync + 'static,
20{
21 rows: Signal<V>,
22 _header: PhantomData<H>,
23 row_id: Rc<dyn Fn(&T) -> Id>,
24 sort_state: Signal<Option<TableSortState<K>>>,
25 sort_cycle: Signal<TableSortCycle>,
26 resizable_columns: Signal<bool>,
27 selectable: Signal<Selectable>,
28 selection_follows_focus: Signal<bool>,
29 selected_row_ids: Signal<Vec<Id>>,
30 on_sort: Option<Arc<dyn Fn(&mut EventContext, K, TableSortDirection) + Send + Sync>>,
31 on_row_select: Option<Box<dyn Fn(&mut EventContext, Id)>>,
32}
33
34enum VirtualTableEvent<K> {
35 RequestSort(K, TableSortDirection),
36 SelectRow(usize),
37}
38
39impl<T, V, Id, H, K> VirtualTable<T, V, Id, H, K>
40where
41 V: Deref<Target = [T]> + Clone + 'static,
42 T: PartialEq + Clone + 'static,
43 Id: PartialEq + Clone + 'static,
44 H: Clone + View,
45 K: Clone + PartialEq + Send + Sync + 'static,
46{
47 pub fn new<S, C, R>(
49 cx: &mut Context,
50 rows: S,
51 columns: C,
52 item_height: f32,
53 row_id: impl Fn(&T) -> Id + 'static,
54 ) -> Handle<Self>
55 where
56 S: Res<V> + 'static,
57 C: Res<R> + 'static,
58 R: Deref<Target = [TableColumn<T, H, K>]> + Clone + 'static,
59 {
60 let row_signal = rows.to_signal(cx);
61 let column_signal = columns.to_signal(cx);
62 let row_id: Rc<dyn Fn(&T) -> Id> = Rc::new(row_id);
63
64 let sort_state = Signal::new(None);
65 let sort_cycle = Signal::new(TableSortCycle::BiState);
66 let resizable_columns = Signal::new(false);
67 let selectable = Signal::new(Selectable::None);
68 let selection_follows_focus = Signal::new(false);
69 let selected_row_ids = Signal::new(Vec::new());
70
71 let selected_indices = Memo::new({
72 let row_id = row_id.clone();
73 move |_| {
74 row_signal.with(|rows| {
75 selected_row_ids.with(|selected_ids| {
76 rows.deref()
77 .iter()
78 .enumerate()
79 .filter_map(|(index, row)| {
80 let id = (row_id)(row);
81 if selected_ids.contains(&id) { Some(index) } else { None }
82 })
83 .collect::<Vec<usize>>()
84 })
85 })
86 }
87 });
88
89 let column_layout = Memo::new(move |_| {
90 column_signal.with(|columns| {
91 columns
92 .deref()
93 .iter()
94 .map(|column| (column.key.clone(), column.hidden.get()))
95 .collect::<Vec<_>>()
96 })
97 });
98
99 Self {
100 rows: row_signal,
101 _header: PhantomData,
102 row_id,
103 sort_state,
104 sort_cycle,
105 resizable_columns,
106 selectable,
107 selection_follows_focus,
108 selected_row_ids,
109 on_sort: None,
110 on_row_select: None,
111 }
112 .build(cx, move |cx| {
113 Binding::new(cx, column_layout, move |cx| {
114 let visible_columns = column_signal.with(|columns| {
115 columns
116 .deref()
117 .iter()
118 .filter(|column| !column.hidden.get())
119 .cloned()
120 .collect::<Vec<_>>()
121 });
122 let last_header_index = visible_columns.len().saturating_sub(1);
123 let header_columns = Rc::new(visible_columns);
124
125 HStack::new(cx, move |cx| {
126 for (column_index, column) in header_columns.iter().cloned().enumerate() {
127 let width_signal = column.width;
128 let sort_state = sort_state;
129 let sort_cycle = sort_cycle;
130 let resizable_columns = resizable_columns;
131 let min_width = column.min_width;
132 let sortable = column.sortable;
133 let resizable = column.resizable;
134 let is_last_column = column_index == last_header_index;
135 let header_content = column.header_content.clone();
136 let column_key = column.key.clone();
137 let sort_direction = sort_state.map({
138 let column_key = column_key.clone();
139 move |state| sort_direction_for_column(state.as_ref(), &column_key)
140 });
141
142 if is_last_column {
143 HStack::new(cx, move |cx| {
144 let header = header_content(cx, sort_direction);
145
146 let column_key = column_key.clone();
147 header.on_press(move |cx| {
148 if sortable.get() {
149 let current_direction = sort_direction_for_column(
150 sort_state.get().as_ref(),
151 &column_key,
152 );
153 let next_direction = next_sort_direction(
154 sort_cycle.get(),
155 current_direction,
156 );
157 cx.emit(VirtualTableEvent::RequestSort(
158 column_key.clone(),
159 next_direction,
160 ));
161 }
162 });
163 })
164 .class("table-header-cell")
165 .toggle_class("sortable", sortable)
166 .toggle_class("not-sortable", sortable.map(|value| !*value))
167 .toggle_class("resizable", false)
168 .width(Stretch(1.0))
169 .min_width(Auto);
170 } else {
171 Resizable::new(
172 cx,
173 width_signal.map(|value| Pixels(*value)),
174 ResizeStackDirection::Right,
175 move |_cx, new_size| {
176 if resizable_columns.get() && resizable.get() {
177 width_signal.set(new_size.max(min_width.get()));
178 }
179 },
180 move |cx| {
181 let header = header_content(cx, sort_direction);
182
183 let column_key = column_key.clone();
184 header.on_press(move |cx| {
185 if sortable.get() {
186 let current_direction = sort_direction_for_column(
187 sort_state.get().as_ref(),
188 &column_key,
189 );
190 let next_direction = next_sort_direction(
191 sort_cycle.get(),
192 current_direction,
193 );
194 cx.emit(VirtualTableEvent::RequestSort(
195 column_key.clone(),
196 next_direction,
197 ));
198 }
199 });
200 },
201 )
202 .class("table-header-cell")
203 .toggle_class("sortable", sortable)
204 .toggle_class("not-sortable", sortable.map(|value| !*value))
205 .toggle_class(
206 "resizable",
207 resizable_columns.map(move |enabled| *enabled && resizable.get()),
208 )
209 .toggle_class(
210 "not-resizable",
211 resizable_columns.map(move |enabled| !*enabled || !resizable.get()),
212 )
213 .min_width(min_width.map(|value| Pixels(*value)));
214 }
215 }
216 })
217 .class("table-header-row")
218 .height(Auto)
219 .width(Stretch(1.0))
220 .min_width(Auto);
221
222 VirtualList::new(cx, row_signal, item_height, move |cx, row_index, row| {
223 HStack::new(cx, |cx| {
224 column_signal.with(|columns| {
225 let visible_columns = columns
226 .deref()
227 .iter()
228 .filter(|column| !column.hidden.get())
229 .collect::<Vec<_>>();
230
231 for (column_index, column) in visible_columns.iter().enumerate() {
232 let width_signal = column.width;
233 let min_width = column.min_width;
234 let cell_content = column.cell_content.clone();
235 let is_last_column = column_index + 1 == visible_columns.len();
236
237 if is_last_column {
238 VStack::new(cx, move |cx| {
239 cell_content(cx, row.map(|value| value.clone()));
240 })
241 .class("table-cell")
242 .width(Stretch(1.0))
243 .min_width(Auto)
244 .height(Percentage(100.0));
245 } else {
246 VStack::new(cx, move |cx| {
247 cell_content(cx, row.map(|value| value.clone()));
248 })
249 .class("table-cell")
250 .width(width_signal.map(|value| Pixels(*value)))
251 .min_width(min_width.map(|value| Pixels(*value)))
252 .height(Percentage(100.0));
253 }
254 }
255 });
256 })
257 .class("table-row")
258 .toggle_class("odd", row_index % 2 == 1)
259 .toggle_class("even", row_index % 2 == 0)
260 .alignment(Alignment::Left)
261 .height(Percentage(100.0))
262 .width(Stretch(1.0))
263 .min_width(Auto)
264 })
265 .width(Stretch(1.0))
266 .min_width(Auto)
267 .height(Stretch(1.0))
268 .min_height(Auto)
269 .class("table-body")
270 .selection(selected_indices)
271 .selectable(selectable)
272 .selection_follows_focus(selection_follows_focus)
273 .on_select(move |cx, index| cx.emit(VirtualTableEvent::<K>::SelectRow(index)));
274 });
275 })
276 .class("table")
277 .role(Role::List)
278 }
279}
280
281impl<T, V, Id, H, K> View for VirtualTable<T, V, Id, H, K>
282where
283 V: Deref<Target = [T]> + Clone + 'static,
284 T: PartialEq + Clone + 'static,
285 Id: PartialEq + Clone + 'static,
286 H: Clone + View,
287 K: Clone + PartialEq + Send + Sync + 'static,
288{
289 fn element(&self) -> Option<&'static str> {
290 Some("virtual-table")
291 }
292
293 fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
294 event.map(|table_event: &VirtualTableEvent<K>, _| match table_event {
295 VirtualTableEvent::RequestSort(column, direction) => {
296 if let Some(callback) = &self.on_sort {
297 (callback)(cx, column.clone(), *direction);
298 }
299 }
300
301 VirtualTableEvent::SelectRow(index) => {
302 let rows = self.rows.get();
303 if let Some(row) = rows.deref().get(*index) {
304 if let Some(callback) = &self.on_row_select {
305 (callback)(cx, (self.row_id)(row));
306 }
307 }
308 }
309 });
310 }
311}
312
313pub trait VirtualTableModifiers<Id, K = String>: Sized
315where
316 K: Clone + PartialEq + Send + Sync + 'static,
317{
318 fn sort_state(self, sort_state: impl Res<Option<TableSortState<K>>> + 'static) -> Self;
319
320 fn resizable_columns<U: Into<bool> + Clone + 'static>(
321 self,
322 flag: impl Res<U> + 'static,
323 ) -> Self;
324
325 fn sort_cycle<U: Into<TableSortCycle> + Clone + 'static>(
326 self,
327 cycle: impl Res<U> + 'static,
328 ) -> Self;
329
330 fn selectable<U: Into<Selectable> + Clone + 'static>(
331 self,
332 selectable: impl Res<U> + 'static,
333 ) -> Self;
334
335 fn selection_follows_focus<U: Into<bool> + Clone + 'static>(
336 self,
337 flag: impl Res<U> + 'static,
338 ) -> Self;
339
340 fn selected_row_ids<R>(self, selected_row_ids: impl Res<R> + 'static) -> Self
341 where
342 R: Deref<Target = [Id]> + Clone + 'static;
343
344 fn on_sort<F>(self, callback: F) -> Self
345 where
346 F: 'static + Fn(&mut EventContext, K, TableSortDirection) + Send + Sync;
347
348 fn on_row_select<F>(self, callback: F) -> Self
349 where
350 F: 'static + Fn(&mut EventContext, Id);
351}
352
353impl<T, V, Id, H, K> VirtualTableModifiers<Id, K> for Handle<'_, VirtualTable<T, V, Id, H, K>>
354where
355 V: Deref<Target = [T]> + Clone + 'static,
356 T: PartialEq + Clone + 'static,
357 Id: PartialEq + Clone + 'static,
358 H: Clone + View,
359 K: Clone + PartialEq + Send + Sync + 'static,
360{
361 fn sort_state(self, sort_state: impl Res<Option<TableSortState<K>>> + 'static) -> Self {
362 let sort_state = sort_state.to_signal(self.cx);
363 self.bind(sort_state, move |handle| {
364 let sort_state = sort_state.get();
365 handle.modify(|table: &mut VirtualTable<T, V, Id, H, K>| {
366 table.sort_state.set(sort_state)
367 });
368 })
369 }
370
371 fn resizable_columns<U: Into<bool> + Clone + 'static>(
372 self,
373 flag: impl Res<U> + 'static,
374 ) -> Self {
375 let flag = flag.to_signal(self.cx);
376 self.bind(flag, move |handle| {
377 let flag = flag.get().into();
378 handle.modify(|table: &mut VirtualTable<T, V, Id, H, K>| {
379 table.resizable_columns.set(flag)
380 });
381 })
382 }
383
384 fn sort_cycle<U: Into<TableSortCycle> + Clone + 'static>(
385 self,
386 cycle: impl Res<U> + 'static,
387 ) -> Self {
388 let cycle = cycle.to_signal(self.cx);
389 self.bind(cycle, move |handle| {
390 let cycle = cycle.get().into();
391 handle.modify(|table: &mut VirtualTable<T, V, Id, H, K>| table.sort_cycle.set(cycle));
392 })
393 }
394
395 fn selectable<U: Into<Selectable> + Clone + 'static>(
396 self,
397 selectable: impl Res<U> + 'static,
398 ) -> Self {
399 let selectable = selectable.to_signal(self.cx);
400 self.bind(selectable, move |handle| {
401 let selectable = selectable.get().into();
402 handle.modify(|table: &mut VirtualTable<T, V, Id, H, K>| {
403 table.selectable.set(selectable)
404 });
405 })
406 }
407
408 fn selection_follows_focus<U: Into<bool> + Clone + 'static>(
409 self,
410 flag: impl Res<U> + 'static,
411 ) -> Self {
412 let flag = flag.to_signal(self.cx);
413 self.bind(flag, move |handle| {
414 let flag = flag.get().into();
415 handle.modify(|table: &mut VirtualTable<T, V, Id, H, K>| {
416 table.selection_follows_focus.set(flag)
417 });
418 })
419 }
420
421 fn selected_row_ids<R>(self, selected_row_ids: impl Res<R> + 'static) -> Self
422 where
423 R: Deref<Target = [Id]> + Clone + 'static,
424 {
425 let selected_row_ids = selected_row_ids.to_signal(self.cx);
426 self.bind(selected_row_ids, move |handle| {
427 let ids = selected_row_ids.with(|ids| ids.deref().to_vec());
428 handle
429 .modify(|table: &mut VirtualTable<T, V, Id, H, K>| table.selected_row_ids.set(ids));
430 })
431 }
432
433 fn on_sort<F>(self, callback: F) -> Self
434 where
435 F: 'static + Fn(&mut EventContext, K, TableSortDirection) + Send + Sync,
436 {
437 self.modify(|table: &mut VirtualTable<T, V, Id, H, K>| {
438 table.on_sort = Some(Arc::new(callback))
439 })
440 }
441
442 fn on_row_select<F>(self, callback: F) -> Self
443 where
444 F: 'static + Fn(&mut EventContext, Id),
445 {
446 self.modify(|table: &mut VirtualTable<T, V, Id, H, K>| {
447 table.on_row_select = Some(Box::new(callback))
448 })
449 }
450}