1use std::{fmt, hash::Hash, rc::Rc};
13
14use gpui_kit::{
15 App, Context, Entity, EntityId, FocusHandle, Focusable as _, Pixels, SharedString,
16 Subscription, WeakEntity, Window,
17};
18
19#[cfg(not(target_family = "wasm"))]
20use super::export::{ExportJob, ExportRequest};
21use super::{
22 ColumnOf, LiveGridDelegate, LiveGridState,
23 chooser::ChooserCommand,
24 columns::{ColumnZone, HeaderLabel},
25 events::{GroupOrigin, LayoutOrigin, LayoutParts, LiveGridEvent, SelectionOrigin, SortOrigin},
26};
27#[cfg(not(target_family = "wasm"))]
28use crate::export::{CsvFormat, ExportError};
29use crate::{
30 engine::{ExpandDepth, GroupBy, SortDirection},
31 export::ExportScope,
32 model::{ColumnId, ColumnIx, GridColumns as _},
33 persist::{GridLayout, LayoutError, LayoutGroup, LayoutReport, LayoutSort},
34 policy::ReorderPolicy,
35};
36
37#[derive(Clone)]
61pub struct LiveGridHandle {
62 ops: Rc<dyn GridOps>,
63}
64
65impl LiveGridHandle {
66 pub fn new<D: LiveGridDelegate>(grid: &Entity<LiveGridState<D>>) -> Self {
68 Self {
69 ops: Rc::new(Ops { grid: grid.clone() }),
70 }
71 }
72
73 pub fn entity_id(&self) -> EntityId {
75 self.ops.entity_id()
76 }
77
78 pub fn downgrade(&self) -> WeakLiveGridHandle {
80 self.ops.downgrade()
81 }
82
83 pub fn focus_handle(&self, cx: &App) -> FocusHandle {
85 self.ops.focus_handle(cx)
86 }
87
88 pub fn columns(&self, cx: &App) -> Vec<ColumnInfo> {
93 self.ops.columns(cx)
94 }
95
96 pub fn column(&self, id: &str, cx: &App) -> Option<ColumnInfo> {
98 self.ops.column(id, cx)
99 }
100
101 pub fn set_column_visible(&self, id: &str, visible: bool, cx: &mut App) -> bool {
105 self.ops.set_column_visible(id, visible, cx)
106 }
107
108 pub fn show_all_columns(&self, cx: &mut App) {
110 self.ops.show_all_columns(cx);
111 }
112
113 pub fn move_column(&self, id: &str, before: Option<&str>, cx: &mut App) -> bool {
118 self.ops.move_column(id, before, cx)
119 }
120
121 pub fn move_band(&self, band: &str, before: Option<&str>, cx: &mut App) -> bool {
126 self.ops.move_band(band, before, cx)
127 }
128
129 pub fn set_column_zone(&self, id: &str, zone: ColumnZone, cx: &mut App) -> bool {
133 self.ops.set_column_zone(id, zone, cx)
134 }
135
136 pub fn set_column_width(&self, id: &str, width: Pixels, cx: &mut App) -> bool {
139 self.ops.set_column_width(id, width, cx)
140 }
141
142 pub fn autofit_columns(&self, cx: &mut App) {
145 self.ops.autofit_columns(cx);
146 }
147
148 pub fn view_id(&self, cx: &App) -> Option<SharedString> {
152 self.ops.view_id(cx)
153 }
154
155 pub fn layout(&self, cx: &App) -> GridLayout {
157 self.ops.layout(cx)
158 }
159
160 pub fn set_layout(
162 &self,
163 layout: &GridLayout,
164 cx: &mut App,
165 ) -> Result<LayoutReport, LayoutError> {
166 self.ops.set_layout(layout, cx)
167 }
168
169 pub fn reset_layout(&self, cx: &mut App) {
171 self.ops.reset_layout(cx);
172 }
173
174 pub fn sort(&self, cx: &App) -> Vec<LayoutSort> {
176 self.ops.sort(cx)
177 }
178
179 pub fn set_sort(&self, sort: impl IntoIterator<Item = LayoutSort>, cx: &mut App) -> bool {
182 self.ops.set_sort(&sort.into_iter().collect::<Vec<_>>(), cx)
183 }
184
185 pub fn group_by(&self, cx: &App) -> Vec<LayoutGroup> {
188 self.ops.group_by(cx)
189 }
190
191 pub fn set_group_by(
196 &self,
197 levels: impl IntoIterator<Item = LayoutGroup>,
198 cx: &mut App,
199 ) -> bool {
200 self.ops
201 .set_group_by(&levels.into_iter().collect::<Vec<_>>(), cx)
202 }
203
204 pub fn expand_depth(&self, cx: &App) -> ExpandDepth {
206 self.ops.expand_depth(cx)
207 }
208
209 pub fn set_expand_depth(&self, depth: ExpandDepth, cx: &mut App) {
212 self.ops.set_expand_depth(depth, cx);
213 }
214
215 pub fn quick_filter(&self, cx: &App) -> SharedString {
217 self.ops.quick_filter(cx)
218 }
219
220 pub fn set_quick_filter(&self, text: impl Into<SharedString>, cx: &mut App) {
222 self.ops.set_quick_filter(text.into(), cx);
223 }
224
225 pub fn row_count(&self, cx: &App) -> usize {
230 self.ops.row_count(cx)
231 }
232
233 pub fn total_row_count(&self, cx: &App) -> usize {
235 self.ops.total_row_count(cx)
236 }
237
238 pub fn selected_count(&self, cx: &App) -> usize {
240 self.ops.selected_count(cx)
241 }
242
243 pub fn is_order_held(&self, cx: &App) -> bool {
246 self.ops.is_order_held(cx)
247 }
248
249 pub fn apply_held_order(&self, cx: &mut App) {
251 self.ops.apply_held_order(cx);
252 }
253
254 pub fn set_reorder_policy(&self, policy: impl ReorderPolicy, cx: &mut App) {
258 self.ops.set_reorder_policy(Box::new(policy), cx);
259 }
260
261 pub fn copy_scope(&self, scope: ExportScope, cx: &mut App) -> bool {
267 self.ops.copy_scope(scope, cx)
268 }
269
270 pub fn is_export_empty(&self, scope: ExportScope, cx: &App) -> bool {
273 self.ops.is_export_empty(scope, cx)
274 }
275
276 #[cfg(not(target_family = "wasm"))]
280 pub fn export_to_path(
281 &self,
282 scope: ExportScope,
283 format: CsvFormat,
284 path: impl Into<std::path::PathBuf>,
285 cx: &mut App,
286 ) -> Result<ExportJob<std::path::PathBuf>, ExportError> {
287 self.ops.export_to_path(scope, format, path.into(), cx)
288 }
289
290 pub fn subscribe(
297 &self,
298 cx: &mut App,
299 on_event: impl FnMut(&GridHandleEvent, &mut App) + 'static,
300 ) -> Subscription {
301 self.ops.subscribe(Box::new(on_event), cx)
302 }
303
304 pub fn observe(&self, cx: &mut App, on_notify: impl FnMut(&mut App) + 'static) -> Subscription {
307 self.ops.observe(Box::new(on_notify), cx)
308 }
309
310 pub fn show_column_chooser(&self, window: &mut Window, cx: &mut App) {
315 self.ops.show_column_chooser(window, cx);
316 }
317
318 pub fn is_column_chooser_open(&self, cx: &App) -> bool {
320 self.ops.is_column_chooser_open(cx)
321 }
322
323 pub(super) fn ops(&self) -> &dyn GridOps {
325 &*self.ops
326 }
327}
328
329impl fmt::Debug for LiveGridHandle {
330 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
331 f.debug_tuple("LiveGridHandle")
332 .field(&self.entity_id())
333 .finish()
334 }
335}
336
337impl<D: LiveGridDelegate> From<&Entity<LiveGridState<D>>> for LiveGridHandle {
338 fn from(grid: &Entity<LiveGridState<D>>) -> Self {
339 Self::new(grid)
340 }
341}
342
343#[derive(Clone)]
345pub struct WeakLiveGridHandle {
346 entity_id: EntityId,
347 upgrade: Rc<dyn Fn() -> Option<LiveGridHandle>>,
348}
349
350impl WeakLiveGridHandle {
351 pub fn upgrade(&self) -> Option<LiveGridHandle> {
353 (self.upgrade)()
354 }
355
356 pub fn entity_id(&self) -> EntityId {
358 self.entity_id
359 }
360}
361
362impl fmt::Debug for WeakLiveGridHandle {
363 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
364 f.debug_tuple("WeakLiveGridHandle")
365 .field(&self.entity_id)
366 .finish()
367 }
368}
369
370#[derive(Clone, Debug, PartialEq, Eq)]
372#[non_exhaustive]
373pub enum GridHandleEvent {
374 LayoutChange {
376 parts: LayoutParts,
378 origin: LayoutOrigin,
380 },
381 SortChange {
383 origin: SortOrigin,
385 },
386 GroupChange {
388 origin: GroupOrigin,
390 },
391 ExpansionChange {
393 origin: GroupOrigin,
395 },
396 ProjectionChange {
398 row_count: usize,
400 store_version: u64,
402 },
403 SelectionChange {
405 count: usize,
407 origin: SelectionOrigin,
409 },
410 OrderHeldChange {
412 held: bool,
414 },
415}
416
417impl GridHandleEvent {
418 fn from_grid_event<K: Eq + Hash, C>(event: &LiveGridEvent<K, C>) -> Option<Self> {
421 Some(match event {
422 LiveGridEvent::LayoutChange { parts, origin, .. } => Self::LayoutChange {
423 parts: *parts,
424 origin: *origin,
425 },
426 LiveGridEvent::SortChange { origin, .. } => Self::SortChange { origin: *origin },
427 LiveGridEvent::GroupChange { origin } => Self::GroupChange { origin: *origin },
428 LiveGridEvent::ExpansionChange { origin, .. } => {
429 Self::ExpansionChange { origin: *origin }
430 }
431 LiveGridEvent::ProjectionChange {
432 row_count,
433 store_version,
434 } => Self::ProjectionChange {
435 row_count: *row_count,
436 store_version: *store_version,
437 },
438 LiveGridEvent::SelectionChange { selection, origin } => Self::SelectionChange {
439 count: selection.len(),
440 origin: *origin,
441 },
442 LiveGridEvent::OrderHeldChange { held } => Self::OrderHeldChange { held: *held },
443 _ => return None,
444 })
445 }
446}
447
448#[derive(Clone, Debug)]
450pub struct ColumnInfo {
451 id: ColumnId,
452 title: SharedString,
453 header_label: HeaderLabel,
454 zone: ColumnZone,
455 visible: bool,
456 width: Pixels,
457 bands: Box<[BandInfo]>,
458 hideable: bool,
459 internal: bool,
460 movable: bool,
461 resizable: bool,
462 sortable: bool,
463 groupable: bool,
464 sort: Option<(SortDirection, usize)>,
465}
466
467impl ColumnInfo {
468 pub fn id(&self) -> &ColumnId {
470 &self.id
471 }
472
473 pub fn title(&self) -> &SharedString {
476 &self.title
477 }
478
479 pub fn icon(&self) -> Option<&SharedString> {
481 match &self.header_label {
482 HeaderLabel::Title => None,
483 HeaderLabel::IconAndTitle(icon) | HeaderLabel::Icon(icon) => Some(icon),
484 }
485 }
486
487 pub fn header_label(&self) -> &HeaderLabel {
489 &self.header_label
490 }
491
492 pub fn zone(&self) -> ColumnZone {
494 self.zone
495 }
496
497 pub fn is_visible(&self) -> bool {
499 self.visible
500 }
501
502 pub fn width(&self) -> Pixels {
504 self.width
505 }
506
507 pub fn bands(&self) -> &[BandInfo] {
509 &self.bands
510 }
511
512 pub fn is_hideable(&self) -> bool {
514 self.hideable
515 }
516
517 pub fn is_internal(&self) -> bool {
520 self.internal
521 }
522
523 pub fn is_movable(&self) -> bool {
525 self.movable
526 }
527
528 pub fn is_resizable(&self) -> bool {
530 self.resizable
531 }
532
533 pub fn is_sortable(&self) -> bool {
535 self.sortable
536 }
537
538 pub fn is_groupable(&self) -> bool {
540 self.groupable
541 }
542
543 pub fn sort(&self) -> Option<SortDirection> {
545 self.sort.map(|(direction, _)| direction)
546 }
547
548 pub fn sort_priority(&self) -> Option<usize> {
551 self.sort.map(|(_, priority)| priority)
552 }
553}
554
555#[derive(Clone, Debug, PartialEq, Eq)]
557pub struct BandInfo {
558 id: &'static str,
559 title: SharedString,
560}
561
562impl BandInfo {
563 pub fn id(&self) -> &'static str {
565 self.id
566 }
567
568 pub fn title(&self) -> &SharedString {
570 &self.title
571 }
572}
573
574impl<D: LiveGridDelegate> LiveGridState<D> {
575 pub fn handle(&self, cx: &Context<Self>) -> LiveGridHandle {
578 LiveGridHandle::new(&cx.entity())
579 }
580
581 pub(super) fn column_by_id(&self, id: &str) -> Option<ColumnOf<D>> {
583 <ColumnOf<D> as crate::model::GridColumns>::all()
584 .iter()
585 .copied()
586 .find(|column| column.id() == id)
587 .filter(|column| self.columns.get(column.ix()).is_some())
588 }
589
590 fn column_info(&self, ix: ColumnIx) -> Option<ColumnInfo> {
592 let column = self.columns.get(ix)?;
593 let sort = self
594 .view
595 .sort()
596 .iter()
597 .enumerate()
598 .find(|(_, (sorted, _))| sorted.ix() == ix)
599 .map(|(priority, (_, direction))| (*direction, priority));
600 Some(ColumnInfo {
601 id: column.column().column_id(),
602 title: column.title().clone(),
603 header_label: column.header_label().clone(),
604 zone: self.columns.zone_of(ix),
605 visible: self.columns.is_visible(ix),
606 width: self.columns.width(ix),
607 bands: self
608 .columns
609 .bands_of(ix)
610 .iter()
611 .map(|&id| BandInfo {
612 id,
613 title: self
614 .columns
615 .band_title(id)
616 .cloned()
617 .unwrap_or_else(|| SharedString::new_static(id)),
618 })
619 .collect(),
620 hideable: column.is_hideable(),
621 internal: column.is_internal(),
622 movable: column.is_movable(),
623 resizable: column.is_resizable(),
624 sortable: column.is_sortable(),
625 groupable: column.is_groupable(),
626 sort,
627 })
628 }
629
630 pub(super) fn band_id(&self, band: &str) -> Option<&'static str> {
632 self.columns
633 .order()
634 .iter()
635 .flat_map(|ix| self.columns.bands_of(*ix).iter().copied())
636 .find(|id| *id == band)
637 }
638}
639
640pub(super) trait GridOps {
642 fn entity_id(&self) -> EntityId;
643 fn downgrade(&self) -> WeakLiveGridHandle;
644 fn focus_handle(&self, cx: &App) -> FocusHandle;
645 fn columns(&self, cx: &App) -> Vec<ColumnInfo>;
646 fn column(&self, id: &str, cx: &App) -> Option<ColumnInfo>;
647 fn set_column_visible(&self, id: &str, visible: bool, cx: &mut App) -> bool;
648 fn show_all_columns(&self, cx: &mut App);
649 fn move_column(&self, id: &str, before: Option<&str>, cx: &mut App) -> bool;
650 fn move_band(&self, band: &str, before: Option<&str>, cx: &mut App) -> bool;
651 fn set_column_zone(&self, id: &str, zone: ColumnZone, cx: &mut App) -> bool;
652 fn set_column_width(&self, id: &str, width: Pixels, cx: &mut App) -> bool;
653 fn autofit_columns(&self, cx: &mut App);
654 fn view_id(&self, cx: &App) -> Option<SharedString>;
655 fn layout(&self, cx: &App) -> GridLayout;
656 fn set_layout(&self, layout: &GridLayout, cx: &mut App) -> Result<LayoutReport, LayoutError>;
657 fn reset_layout(&self, cx: &mut App);
658 fn sort(&self, cx: &App) -> Vec<LayoutSort>;
659 fn set_sort(&self, sort: &[LayoutSort], cx: &mut App) -> bool;
660 fn group_by(&self, cx: &App) -> Vec<LayoutGroup>;
661 fn set_group_by(&self, levels: &[LayoutGroup], cx: &mut App) -> bool;
662 fn expand_depth(&self, cx: &App) -> ExpandDepth;
663 fn set_expand_depth(&self, depth: ExpandDepth, cx: &mut App);
664 fn quick_filter(&self, cx: &App) -> SharedString;
665 fn set_quick_filter(&self, text: SharedString, cx: &mut App);
666 fn row_count(&self, cx: &App) -> usize;
667 fn total_row_count(&self, cx: &App) -> usize;
668 fn selected_count(&self, cx: &App) -> usize;
669 fn is_order_held(&self, cx: &App) -> bool;
670 fn apply_held_order(&self, cx: &mut App);
671 fn set_reorder_policy(&self, policy: Box<dyn ReorderPolicy>, cx: &mut App);
672 fn copy_scope(&self, scope: ExportScope, cx: &mut App) -> bool;
673 fn is_export_empty(&self, scope: ExportScope, cx: &App) -> bool;
674 #[cfg(not(target_family = "wasm"))]
675 fn export_to_path(
676 &self,
677 scope: ExportScope,
678 format: CsvFormat,
679 path: std::path::PathBuf,
680 cx: &mut App,
681 ) -> Result<ExportJob<std::path::PathBuf>, ExportError>;
682 fn subscribe(
683 &self,
684 on_event: Box<dyn FnMut(&GridHandleEvent, &mut App)>,
685 cx: &mut App,
686 ) -> Subscription;
687 fn observe(&self, on_notify: Box<dyn FnMut(&mut App)>, cx: &mut App) -> Subscription;
688 fn show_column_chooser(&self, window: &mut Window, cx: &mut App);
690 fn is_column_chooser_open(&self, cx: &App) -> bool;
691 fn chooser(
692 &self,
693 command: ChooserCommand<'_>,
694 window: Option<&mut Window>,
695 cx: &mut App,
696 ) -> bool;
697}
698
699struct Ops<D: LiveGridDelegate> {
701 grid: Entity<LiveGridState<D>>,
702}
703
704impl<D: LiveGridDelegate> Ops<D> {
705 fn on_column(
708 &self,
709 id: &str,
710 cx: &mut App,
711 command: impl FnOnce(&mut LiveGridState<D>, ColumnOf<D>, &mut Context<LiveGridState<D>>) -> bool,
712 ) -> bool {
713 self.grid
714 .update(cx, |grid, cx| match grid.column_by_id(id) {
715 Some(column) => command(grid, column, cx),
716 None => false,
717 })
718 }
719}
720
721impl<D: LiveGridDelegate> GridOps for Ops<D> {
722 fn entity_id(&self) -> EntityId {
723 self.grid.entity_id()
724 }
725
726 fn downgrade(&self) -> WeakLiveGridHandle {
727 let grid: WeakEntity<LiveGridState<D>> = self.grid.downgrade();
728 WeakLiveGridHandle {
729 entity_id: self.grid.entity_id(),
730 upgrade: Rc::new(move || grid.upgrade().map(|grid| LiveGridHandle::new(&grid))),
731 }
732 }
733
734 fn focus_handle(&self, cx: &App) -> FocusHandle {
735 self.grid.read(cx).focus_handle(cx)
736 }
737
738 fn columns(&self, cx: &App) -> Vec<ColumnInfo> {
739 let grid = self.grid.read(cx);
740 grid.columns
741 .order()
742 .iter()
743 .filter_map(|ix| grid.column_info(*ix))
744 .collect()
745 }
746
747 fn column(&self, id: &str, cx: &App) -> Option<ColumnInfo> {
748 let grid = self.grid.read(cx);
749 grid.column_info(grid.column_by_id(id)?.ix())
750 }
751
752 fn set_column_visible(&self, id: &str, visible: bool, cx: &mut App) -> bool {
753 self.on_column(id, cx, |grid, column, cx| {
754 grid.set_column_visible(column, visible, cx)
755 })
756 }
757
758 fn show_all_columns(&self, cx: &mut App) {
759 self.grid.update(cx, LiveGridState::show_all_columns);
760 }
761
762 fn move_column(&self, id: &str, before: Option<&str>, cx: &mut App) -> bool {
763 self.grid.update(cx, |grid, cx| {
764 let Some(column) = grid.column_by_id(id) else {
765 return false;
766 };
767 let before = match before {
768 Some(before) => match grid.column_by_id(before) {
769 Some(before) => Some(before),
770 None => return false,
771 },
772 None => None,
773 };
774 grid.move_column(column, before, cx)
775 })
776 }
777
778 fn move_band(&self, band: &str, before: Option<&str>, cx: &mut App) -> bool {
779 self.grid.update(cx, |grid, cx| {
780 let Some(band) = grid.band_id(band) else {
781 return false;
782 };
783 let before = match before {
784 Some(before) => match grid.column_by_id(before) {
785 Some(before) => Some(before),
786 None => return false,
787 },
788 None => None,
789 };
790 grid.move_band(band, before, cx)
791 })
792 }
793
794 fn set_column_zone(&self, id: &str, zone: ColumnZone, cx: &mut App) -> bool {
795 self.on_column(id, cx, |grid, column, cx| {
796 grid.set_column_zone(column, zone, cx);
797 true
798 })
799 }
800
801 fn set_column_width(&self, id: &str, width: Pixels, cx: &mut App) -> bool {
802 self.on_column(id, cx, |grid, column, cx| {
803 grid.set_column_width(column, width, cx);
804 true
805 })
806 }
807
808 fn autofit_columns(&self, cx: &mut App) {
809 self.grid.update(cx, LiveGridState::autofit_columns);
810 }
811
812 fn view_id(&self, cx: &App) -> Option<SharedString> {
813 self.grid.read(cx).view_id().cloned()
814 }
815
816 fn layout(&self, cx: &App) -> GridLayout {
817 self.grid.read(cx).layout()
818 }
819
820 fn set_layout(&self, layout: &GridLayout, cx: &mut App) -> Result<LayoutReport, LayoutError> {
821 self.grid.update(cx, |grid, cx| grid.set_layout(layout, cx))
822 }
823
824 fn reset_layout(&self, cx: &mut App) {
825 self.grid.update(cx, LiveGridState::reset_layout);
826 }
827
828 fn sort(&self, cx: &App) -> Vec<LayoutSort> {
829 self.grid
830 .read(cx)
831 .sort()
832 .iter()
833 .map(|(column, direction)| LayoutSort::new(column.column_id(), *direction))
834 .collect()
835 }
836
837 fn set_sort(&self, sort: &[LayoutSort], cx: &mut App) -> bool {
838 self.grid.update(cx, |grid, cx| {
839 let Some(sort) = sort
840 .iter()
841 .map(|level| {
842 Some((
843 grid.column_by_id(level.column().as_ref())?,
844 level.direction(),
845 ))
846 })
847 .collect::<Option<Vec<_>>>()
848 else {
849 return false;
850 };
851 grid.set_sort(sort, cx);
852 true
853 })
854 }
855
856 fn group_by(&self, cx: &App) -> Vec<LayoutGroup> {
857 self.grid
858 .read(cx)
859 .group_by()
860 .iter()
861 .map(|level| LayoutGroup::new(level.id(), level.direction()))
862 .collect()
863 }
864
865 fn set_group_by(&self, levels: &[LayoutGroup], cx: &mut App) -> bool {
866 self.grid.update(cx, |grid, cx| {
867 let Some(levels) = levels
868 .iter()
869 .map(|level| {
870 let column = grid.column_by_id(level.column().as_ref())?;
871 Some(GroupBy::column(column).with_direction(level.direction()))
872 })
873 .collect::<Option<Vec<_>>>()
874 else {
875 return false;
876 };
877 grid.set_group_by(levels, cx);
878 true
879 })
880 }
881
882 fn expand_depth(&self, cx: &App) -> ExpandDepth {
883 self.grid.read(cx).expand_depth()
884 }
885
886 fn set_expand_depth(&self, depth: ExpandDepth, cx: &mut App) {
887 self.grid
888 .update(cx, |grid, cx| grid.set_expand_depth(depth, cx));
889 }
890
891 fn quick_filter(&self, cx: &App) -> SharedString {
892 self.grid.read(cx).quick_filter().clone()
893 }
894
895 fn set_quick_filter(&self, text: SharedString, cx: &mut App) {
896 self.grid
897 .update(cx, |grid, cx| grid.set_quick_filter(text, cx));
898 }
899
900 fn row_count(&self, cx: &App) -> usize {
901 self.grid.read(cx).projection.row_count()
902 }
903
904 fn total_row_count(&self, cx: &App) -> usize {
905 self.grid.read(cx).snapshot.len()
906 }
907
908 fn selected_count(&self, cx: &App) -> usize {
909 self.grid.read(cx).selection().len()
910 }
911
912 fn is_order_held(&self, cx: &App) -> bool {
913 self.grid.read(cx).is_order_held()
914 }
915
916 fn apply_held_order(&self, cx: &mut App) {
917 self.grid.update(cx, LiveGridState::apply_held_order);
918 }
919
920 fn set_reorder_policy(&self, policy: Box<dyn ReorderPolicy>, cx: &mut App) {
921 self.grid.update(cx, |grid, cx| {
922 grid.reorder = policy;
924 grid.reconsider_held(cx);
925 });
926 }
927
928 fn copy_scope(&self, scope: ExportScope, cx: &mut App) -> bool {
929 self.grid.update(cx, |grid, cx| grid.copy_scope(scope, cx))
930 }
931
932 fn is_export_empty(&self, scope: ExportScope, cx: &App) -> bool {
933 self.grid.read(cx).is_export_empty(scope)
934 }
935
936 #[cfg(not(target_family = "wasm"))]
937 fn export_to_path(
938 &self,
939 scope: ExportScope,
940 format: CsvFormat,
941 path: std::path::PathBuf,
942 cx: &mut App,
943 ) -> Result<ExportJob<std::path::PathBuf>, ExportError> {
944 self.grid.update(cx, |grid, cx| {
945 grid.export_to_path(ExportRequest::new(scope), format, path, cx)
946 })
947 }
948
949 fn subscribe(
950 &self,
951 mut on_event: Box<dyn FnMut(&GridHandleEvent, &mut App)>,
952 cx: &mut App,
953 ) -> Subscription {
954 cx.subscribe(&self.grid, move |_, event: &LiveGridEvent<_, _>, cx| {
955 if let Some(event) = GridHandleEvent::from_grid_event(event) {
956 on_event(&event, cx);
957 }
958 })
959 }
960
961 fn observe(&self, mut on_notify: Box<dyn FnMut(&mut App)>, cx: &mut App) -> Subscription {
962 cx.observe(&self.grid, move |_, cx| on_notify(cx))
963 }
964
965 fn show_column_chooser(&self, window: &mut Window, cx: &mut App) {
966 self.grid
967 .update(cx, |grid, cx| grid.show_column_chooser(window, cx));
968 }
969
970 fn is_column_chooser_open(&self, cx: &App) -> bool {
971 self.grid.read(cx).is_column_chooser_open()
972 }
973
974 fn chooser(
975 &self,
976 command: ChooserCommand<'_>,
977 window: Option<&mut Window>,
978 cx: &mut App,
979 ) -> bool {
980 self.grid
981 .update(cx, |grid, cx| grid.run_chooser_command(command, window, cx))
982 }
983}
984
985#[cfg(test)]
986mod tests {
987 use super::*;
988 use crate::state::selection::GridSelection;
989
990 #[test]
991 fn test_handle_event_translation() {
992 let event = |event: LiveGridEvent<u32, u16>| GridHandleEvent::from_grid_event(&event);
993 assert_eq!(
994 event(LiveGridEvent::OrderHeldChange { held: true }),
995 Some(GridHandleEvent::OrderHeldChange { held: true })
996 );
997 assert_eq!(
998 event(LiveGridEvent::ProjectionChange {
999 row_count: 12,
1000 store_version: 3
1001 }),
1002 Some(GridHandleEvent::ProjectionChange {
1003 row_count: 12,
1004 store_version: 3
1005 })
1006 );
1007 assert_eq!(
1008 event(LiveGridEvent::SelectionChange {
1009 selection: GridSelection::default(),
1010 origin: SelectionOrigin::Programmatic,
1011 }),
1012 Some(GridHandleEvent::SelectionChange {
1013 count: 0,
1014 origin: SelectionOrigin::Programmatic
1015 })
1016 );
1017 assert_eq!(
1019 event(LiveGridEvent::Confirm {
1020 keys: [1].into(),
1021 secondary: false
1022 }),
1023 None
1024 );
1025 }
1026}