1mod a11y;
6mod change;
8mod click;
10mod columns;
11mod chooser;
13mod copy;
15mod export;
17mod disclosure;
19mod drag;
21mod edit;
23mod editors;
25mod events;
26mod fade;
28mod frame;
29mod ghost;
31mod group_panel;
33mod handle;
35mod link;
37mod grouping;
39mod groups;
41mod header;
42mod images;
44mod layout;
45mod menu;
46mod pending;
48mod pinned;
49mod progress;
50mod projection;
51mod scroll;
52mod selection;
53mod stats;
54mod style;
55mod summary;
57mod tooltip;
58mod tree;
60mod widgets;
62
63use std::{
64 hash::{Hash, Hasher as _},
65 sync::Arc,
66};
67
68use gpui_kit::base::{AutoScroll, Scrollbar, TestSupportExt as _};
69use gpui_kit::component::{ActiveTheme as _, Size as ComponentSize};
70use gpui_kit::{
71 App, Bounds, Context, EventEmitter, FocusHandle, Focusable, InteractiveElement as _,
72 IntoElement, KeyContext, ParentElement as _, Pixels, Render, SharedString,
73 StatefulInteractiveElement as _, Styled as _, Task, Window, div, prelude::FluentBuilder as _,
74 px,
75};
76use web_time::Instant;
77
78pub(crate) use self::{
79 a11y::{A11yPlan, FOCUS_CELL_ID},
80 tooltip::CellHover,
81};
82use self::{
83 a11y::{RowNumbering, root_role},
84 click::PendingClick,
85 columns::ColumnLayout,
86 frame::PaintedLine,
87 ghost::GhostTextStyle,
88 grouping::ExpansionRevision,
89 groups::CaptionTexts,
90 header::HeaderState,
91 menu::OpenMenu,
92 pinned::PaintedBands,
93 progress::ProgressSlot,
94 projection::{Coverage, HeldOrder, PendingWork},
95 scroll::{GridScrollHandle, LineAnchor, ScrollReset},
96 selection::{PointerTarget, Press, SelectionModel},
97 summary::SummaryTexts,
98 tooltip::TooltipState,
99};
100pub use self::{
101 change::{ChangePaint, ChangePainter, ChangePalette, DirectionalChanges, UniformChanges},
102 chooser::{
103 ColumnChooserContext, ColumnChooserFactory, ColumnChooserPanel, PopoverColumnChooser,
104 },
105 columns::{
106 CellContent, ColumnAlign, ColumnBand, ColumnZone, GridColumn, GridSchema, HeaderLabel,
107 MIN_COLUMN_WIDTH, ViewColumnIx,
108 },
109 drag::{DragPayload, DropPlace, DropSource, DropTarget, RowDrag, RowDragContext},
110 edit::{
111 CellEditor, EditCancelReason, EditError, EditId, EditInput, EditMessageContext, EditOrigin,
112 EditState, EditValidateContext, EditableContext, EditorCell, EditorField, EditorMark,
113 EditorOpen, EditorStart,
114 },
115 editors::{
116 CellEditHandle, CheckboxEditor, DateEditor, NumberEditor, NumberKind, SelectEditor,
117 SelectOption, TextEditor,
118 },
119 events::{
120 ExpandTarget, GroupOrigin, LayoutOrigin, LayoutPart, LayoutParts, LiveGridEvent,
121 SelectionOrigin, SortOrigin,
122 },
123 export::{EmptyExport, ExportHeader, ExportJob, ExportOutcome, ExportProgress, ExportRequest},
124 groups::{CaptionTag, GroupCaptionContext},
125 handle::{BandInfo, ColumnInfo, GridHandleEvent, LiveGridHandle, WeakLiveGridHandle},
126 images::{AssetImageSource, CellImage, CellImageSource, CellImages, ImageFit},
127 link::{LinkOpener, OpenInBrowser},
128 menu::{GridMenuContext, GridTarget},
129 progress::ProgressContext,
130 projection::FlushOutcome,
131 selection::{FocusedCell, GridSelection, ScopedSelection, SelectionMode},
132 stats::{GridStats, GridTuning},
133 style::{
134 AggregateLine, AggregateStyleContext, CellChange, CellStyle, CellStyleContext,
135 LiveGridDelegate,
136 },
137 tooltip::{CellTooltip, CellTooltipContext, HeaderTooltipContext},
138 widgets::CellElementContext,
139};
140use crate::{
141 actions::{CELL_CONTENT_KEY, KEY_CONTEXT},
142 element::GridBody,
143 engine::{GroupPath, Projection, ProjectionEngine, Rebuild, ViewSpec},
144 model::{ColumnIx, ColumnSet, GridColumns as _, GridRow},
145 policy::{
146 AutofitPolicy, BackgroundRunner, BandedMove, ColumnMovePolicy, ContextMenuGesture,
147 ExpandGesture, FocusCaptions, LiveReorder, ProjectionScope, ReorderPolicy,
148 SelectionGesture, SelectionPolicy, SelectionScope, SortCycle, StandardContextMenuGesture,
149 StandardExpandGesture, StandardSelectionGesture, StandardSortCycle, TaskRunner,
150 VisibleRowsAutofit,
151 },
152 quick_filter::{ContainsAllTerms, QuickFilterMatcher},
153 store::{GridStore, GridWriter, PinnedEdge, StoreOptions, StoreSnapshot},
154 text::{TextCaches, TextStats},
155};
156
157type ColumnOf<D> = <<D as LiveGridDelegate>::Row as GridRow>::Column;
158type KeyOf<D> = <<D as LiveGridDelegate>::Row as GridRow>::Key;
159
160const CELL_PADDING: Pixels = px(6.);
161
162pub(crate) fn key_hash<K: Hash + ?Sized>(key: &K) -> u64 {
170 let mut hasher = rustc_hash::FxHasher::default();
171 key.hash(&mut hasher);
172 hasher.finish()
173}
174
175pub(crate) fn group_hash(path: &GroupPath) -> u64 {
180 key_hash(&("live-grid-group", path))
181}
182
183pub(crate) fn footer_hash(path: &GroupPath) -> u64 {
186 key_hash(&("live-grid-group-footer", path))
187}
188
189pub(crate) fn totals_hash(edge: PinnedEdge) -> u64 {
192 key_hash(&(
193 "live-grid-totals",
194 match edge {
195 PinnedEdge::Top => "top",
196 PinnedEdge::Bottom => "bottom",
197 },
198 ))
199}
200
201pub struct LiveGridState<D: LiveGridDelegate> {
203 delegate: D,
204 store: GridStore<D::Row>,
205 snapshot: Arc<StoreSnapshot<D::Row>>,
206 projection: Arc<Projection>,
207 view: ViewSpec<D::Row>,
208 engine: Arc<dyn ProjectionEngine<D::Row>>,
209 tuning: GridTuning,
210 pending: PendingWork,
211 rebuild_task: Option<Task<()>>,
212 rebuild_timer: Option<Task<()>>,
213 last_rebuild_started: Option<Instant>,
214 pending_user_action: bool,
217 pending_scroll_reset: ScrollReset,
221 expansion_revision: ExpansionRevision,
223 shown_revision: ExpansionRevision,
225 aggregates_changed: bool,
228 expand_anchor: Option<LineAnchor>,
231 expand_gesture: Box<dyn ExpandGesture>,
233 caption_texts: CaptionTexts,
235 totals: Option<PinnedEdge>,
237 summary_texts: SummaryTexts,
238 reorder: Box<dyn ReorderPolicy>,
239 runner: Box<dyn TaskRunner>,
240 sort_cycle: Box<dyn SortCycle>,
241 quick_filter_matcher: Arc<dyn QuickFilterMatcher>,
242 quick_filter_columns: Option<ColumnSet>,
244 quick_filter_text: SharedString,
246 held: Option<HeldOrder>,
247 pointer_inside: bool,
248 last_removal_version: u64,
251 columns: ColumnLayout<ColumnOf<D>>,
252 layout: layout::LayoutSlot<D::Row>,
254 column_move: Box<dyn ColumnMovePolicy>,
255 autofit: Box<dyn AutofitPolicy>,
256 pending_autofit: Option<(ColumnSet, LayoutOrigin)>,
258 current_column: Option<ColumnIx>,
260 header: HeaderState,
261 scroll_row: usize,
262 scroll_offset: Pixels,
263 scroll_x: Pixels,
264 row_height: Pixels,
265 body_bounds: Bounds<Pixels>,
266 painted: Vec<PaintedLine>,
270 painted_pinned: PaintedBands<D::Row>,
272 coverage: Coverage,
275 progress: ProgressSlot,
277 scroll_handle: GridScrollHandle,
279 selection: SelectionModel<KeyOf<D>, ColumnOf<D>>,
281 selection_scope: Box<dyn SelectionScope<D::Row>>,
282 selection_policy: Box<dyn SelectionPolicy<D::Row>>,
284 selection_gesture: Box<dyn SelectionGesture>,
285 context_menu_gesture: Box<dyn ContextMenuGesture>,
286 pointer_target: Option<PointerTarget<KeyOf<D>>>,
288 press: Option<Press<KeyOf<D>, ColumnOf<D>>>,
290 press_series: Option<KeyOf<D>>,
293 click: Option<PendingClick<KeyOf<D>, ColumnOf<D>>>,
296 drag_scroll: AutoScroll,
298 menu: Option<OpenMenu<KeyOf<D>, ColumnOf<D>>>,
300 tooltip: TooltipState,
303 #[cfg(any(test, feature = "test-support"))]
305 focus_visible: bool,
306 accessibility_label: Option<SharedString>,
308 #[cfg(any(test, feature = "test-support"))]
311 a11y_forced: bool,
312 #[cfg(any(test, feature = "test-support"))]
314 last_a11y: Option<std::rc::Rc<A11yPlan>>,
315 text_cache: TextCaches,
316 change_paint: ChangePaint,
318 #[cfg(any(test, feature = "test-support"))]
320 test_cell_lead: Option<(ColumnIx, Pixels, Option<SharedString>)>,
321 #[cfg(any(test, feature = "test-support"))]
323 test_cell_host: Option<frame::TestCellHost>,
324 #[cfg(any(test, feature = "test-support"))]
326 last_paint_order: Vec<&'static str>,
327 #[cfg(any(test, feature = "test-support"))]
329 painted_icons: Vec<(Bounds<Pixels>, SharedString, gpui_kit::Hsla)>,
330 #[cfg(any(test, feature = "test-support"))]
332 painted_images: Vec<frame::PaintedImage>,
333 #[cfg(any(test, feature = "test-support"))]
335 painted_pills: Vec<(Bounds<Pixels>, gpui_kit::Hsla)>,
336 fade: Option<(u32, Task<()>)>,
337 stats: GridStats,
338 focus_handle: FocusHandle,
339 copy: copy::CopySlot<D::Row>,
341 column_chooser: chooser::ChooserSlot,
343 group_panel: group_panel::GroupPanelState,
345 edit_setup: edit::EditSetup<D::Row>,
348 edit: edit::EditSlot<KeyOf<D>, ColumnOf<D>>,
349 pending_edits: pending::PendingEdits<KeyOf<D>, ColumnOf<D>>,
350 widgets: widgets::WidgetSlot<KeyOf<D>, ColumnOf<D>>,
351 links: link::LinkSlot,
352 images: images::CellImages,
354 drag: drag::DragSlot<D>,
356 _pull: Task<()>,
357}
358
359impl<D: LiveGridDelegate> LiveGridState<D> {
360 pub fn new(
362 schema: GridSchema<ColumnOf<D>>,
363 delegate: D,
364 store: GridStore<D::Row>,
365 cx: &mut Context<Self>,
366 ) -> Self {
367 let subscription = store.subscribe();
368 let snapshot = store.snapshot();
369 let pull = cx.spawn(async move |this, cx| {
370 while subscription.changed().await {
371 let (snapshot, changes) = subscription.take();
372 let Ok(interval) = this.update(cx, |state, cx| {
373 state.apply_snapshot(snapshot, changes, cx);
374 state.tuning.pull_interval
375 }) else {
376 break;
377 };
378 cx.background_executor().timer(interval).await;
379 }
380 });
381 Self {
382 delegate,
383 progress: ProgressSlot::new(snapshot.progress()),
384 snapshot,
385 store,
386 projection: Arc::new(Projection::default()),
387 view: ViewSpec::new(),
388 engine: Arc::new(Rebuild::default()),
389 tuning: GridTuning::default(),
390 pending: PendingWork::Rebuild { expand: false },
391 rebuild_task: None,
392 rebuild_timer: None,
393 last_rebuild_started: None,
394 pending_user_action: true,
395 pending_scroll_reset: ScrollReset::Keep,
396 expansion_revision: ExpansionRevision::default(),
397 shown_revision: ExpansionRevision::default(),
398 aggregates_changed: false,
399 expand_anchor: None,
400 expand_gesture: Box::new(StandardExpandGesture),
401 caption_texts: CaptionTexts::default(),
402 totals: None,
403 summary_texts: SummaryTexts::default(),
404 reorder: Box::new(LiveReorder),
405 runner: Box::new(BackgroundRunner),
406 sort_cycle: Box::new(StandardSortCycle),
407 quick_filter_matcher: Arc::new(ContainsAllTerms),
408 quick_filter_columns: None,
409 quick_filter_text: SharedString::default(),
410 held: None,
411 pointer_inside: false,
412 last_removal_version: 0,
413 layout: layout::LayoutSlot::new(&schema),
415 columns: {
416 let mut columns = ColumnLayout::new(schema);
417 columns.normalize(&BandedMove);
418 columns
419 },
420 column_move: Box::new(BandedMove),
421 autofit: Box::new(VisibleRowsAutofit),
422 pending_autofit: None,
423 current_column: None,
424 header: HeaderState::default(),
425 scroll_row: 0,
426 scroll_offset: px(0.),
427 scroll_x: px(0.),
428 row_height: ComponentSize::default().table_row_height(),
430 body_bounds: Bounds::default(),
431 painted: Vec::new(),
432 painted_pinned: PaintedBands::default(),
433 coverage: Coverage::default(),
434 scroll_handle: GridScrollHandle::default(),
435 selection: SelectionModel::new(SelectionMode::default()),
436 selection_scope: Box::new(ProjectionScope),
437 selection_policy: Box::new(FocusCaptions),
438 selection_gesture: Box::new(StandardSelectionGesture),
439 context_menu_gesture: Box::new(StandardContextMenuGesture),
440 pointer_target: None,
441 press: None,
442 press_series: None,
443 click: None,
444 drag_scroll: AutoScroll::default(),
445 menu: None,
446 tooltip: TooltipState::default(),
447 #[cfg(any(test, feature = "test-support"))]
448 focus_visible: false,
449 accessibility_label: None,
450 #[cfg(any(test, feature = "test-support"))]
451 a11y_forced: false,
452 #[cfg(any(test, feature = "test-support"))]
453 last_a11y: None,
454 text_cache: TextCaches::default(),
455 change_paint: ChangePaint::default(),
456 #[cfg(any(test, feature = "test-support"))]
457 test_cell_lead: None,
458 #[cfg(any(test, feature = "test-support"))]
459 test_cell_host: None,
460 #[cfg(any(test, feature = "test-support"))]
461 last_paint_order: Vec::new(),
462 #[cfg(any(test, feature = "test-support"))]
463 painted_icons: Vec::new(),
464 #[cfg(any(test, feature = "test-support"))]
465 painted_images: Vec::new(),
466 #[cfg(any(test, feature = "test-support"))]
467 painted_pills: Vec::new(),
468 fade: None,
469 stats: GridStats::default(),
470 focus_handle: cx.focus_handle().tab_stop(true),
472 copy: copy::CopySlot::default(),
473 column_chooser: chooser::ChooserSlot::default(),
474 group_panel: group_panel::GroupPanelState::default(),
475 edit_setup: edit::EditSetup::default(),
476 edit: edit::EditSlot::default(),
477 pending_edits: pending::PendingEdits::default(),
478 widgets: widgets::WidgetSlot::default(),
479 links: link::LinkSlot::default(),
480 images: images::CellImages::default(),
481 drag: drag::DragSlot::default(),
482 _pull: pull,
483 }
484 }
485
486 pub fn new_standalone(
488 schema: GridSchema<ColumnOf<D>>,
489 delegate: D,
490 options: StoreOptions,
491 cx: &mut Context<Self>,
492 ) -> Self {
493 let store = GridStore::new(options, cx);
494 Self::new(schema, delegate, store, cx)
495 }
496
497 pub fn with_view(mut self, view: ViewSpec<D::Row>) -> Self {
504 let quick_filter = self.view.quick_filter().cloned();
505 let expansion = self.view.expansion().clone();
506 self.view = view
507 .with_quick_filter(quick_filter)
508 .with_expansion(expansion);
509 self.layout.set_view(&self.view);
511 self.apply_initial_layout()
512 }
513
514 pub fn with_engine(mut self, engine: impl ProjectionEngine<D::Row>) -> Self {
516 self.engine = Arc::new(engine);
517 self
518 }
519
520 pub fn with_tuning(mut self, tuning: GridTuning) -> Self {
522 self.tuning = tuning;
523 self
524 }
525
526 pub fn with_reorder_policy(mut self, policy: impl ReorderPolicy) -> Self {
529 self.reorder = Box::new(policy);
530 self
531 }
532
533 pub fn with_task_runner(mut self, runner: impl TaskRunner) -> Self {
535 self.runner = Box::new(runner);
536 self
537 }
538
539 pub fn with_sort_cycle(mut self, cycle: impl SortCycle) -> Self {
542 self.sort_cycle = Box::new(cycle);
543 self
544 }
545
546 pub fn with_quick_filter_matcher(mut self, matcher: impl QuickFilterMatcher) -> Self {
549 self.quick_filter_matcher = Arc::new(matcher);
550 self.recompile_quick_filter();
551 self
552 }
553
554 pub fn with_quick_filter_columns(
558 mut self,
559 columns: impl IntoIterator<Item = ColumnOf<D>>,
560 ) -> Self {
561 let set = columns
562 .into_iter()
563 .fold(ColumnSet::EMPTY, |mut set, column| {
564 set.insert(column.ix());
565 set
566 });
567 self.quick_filter_columns = Some(set);
568 self.recompile_quick_filter();
569 self
570 }
571
572 pub(crate) fn set_row_height(&mut self, row_height: Pixels) {
574 if self.row_height != row_height {
575 self.row_height = row_height;
576 self.clamp_scroll();
577 }
578 }
579
580 pub(crate) fn set_accessibility_label(&mut self, label: Option<SharedString>) {
582 self.accessibility_label = label;
583 }
584
585 #[cfg(any(test, feature = "test-support"))]
591 pub fn set_a11y_forced(&mut self, forced: bool) {
592 self.a11y_forced = forced;
593 }
594
595 #[cfg(any(test, feature = "test-support"))]
600 pub fn a11y_focus_description(&self) -> Option<&str> {
601 self.last_a11y.as_ref()?.focus_description()
602 }
603
604 #[cfg(any(test, feature = "test-support"))]
610 pub fn set_test_cell_lead(
611 &mut self,
612 column: ColumnOf<D>,
613 indent: Pixels,
614 icon: Option<SharedString>,
615 ) {
616 self.test_cell_lead = Some((column.ix(), indent, icon));
617 }
618
619 #[cfg(any(test, feature = "test-support"))]
627 pub fn set_test_cell_host(
628 &mut self,
629 column: Option<ColumnOf<D>>,
630 build: impl Fn(gpui_kit::Stateful<gpui_kit::Div>) -> gpui_kit::Stateful<gpui_kit::Div> + 'static,
631 ) {
632 self.test_cell_host = column.map(|column| {
633 let build: std::rc::Rc<
634 dyn Fn(gpui_kit::Stateful<gpui_kit::Div>) -> gpui_kit::Stateful<gpui_kit::Div>,
635 > = std::rc::Rc::new(build);
636 (column.ix(), build)
637 });
638 }
639
640 #[cfg(any(test, feature = "test-support"))]
646 pub fn last_paint_order(&self) -> &[&'static str] {
647 &self.last_paint_order
648 }
649
650 #[cfg(any(test, feature = "test-support"))]
651 pub(crate) fn set_last_paint_order(&mut self, order: Vec<&'static str>) {
652 self.last_paint_order = order;
653 }
654
655 pub(crate) fn set_last_hosts_prepaint(&mut self, elapsed: std::time::Duration) {
658 self.stats.last_hosts_prepaint = elapsed;
659 }
660
661 fn is_a11y_planned(&self, window: &Window) -> bool {
665 #[cfg(any(test, feature = "test-support"))]
666 if self.a11y_forced {
667 return true;
668 }
669 window.is_a11y_active()
670 }
671
672 fn root_key_context(&self) -> KeyContext {
676 let mut context = KeyContext::default();
677 context.add(KEY_CONTEXT);
678 let content = self
679 .selection
680 .focus()
681 .filter(|focus| focus.row().is_some())
682 .map(|focus| self.columns.column(focus.column().ix()).content);
683 if self.is_link_focused() {
684 context.set(CELL_CONTENT_KEY, "link");
685 } else if let Some(content) = content {
686 context.set(CELL_CONTENT_KEY, content.key_value());
687 }
688 context
689 }
690
691 fn a11y_numbering(&self) -> RowNumbering {
695 RowNumbering::new(
696 self.snapshot.pinned_rows(PinnedEdge::Top).len(),
697 self.projection.len(),
698 self.snapshot.pinned_rows(PinnedEdge::Bottom).len(),
699 )
700 .with_totals(self.totals)
701 }
702
703 pub fn store(&self) -> &GridStore<D::Row> {
705 &self.store
706 }
707
708 pub fn writer(&self) -> GridWriter<D::Row> {
710 self.store.writer()
711 }
712
713 pub fn snapshot(&self) -> &Arc<StoreSnapshot<D::Row>> {
715 &self.snapshot
716 }
717
718 pub fn projection(&self) -> &Arc<Projection> {
720 &self.projection
721 }
722
723 pub fn stats(&self) -> GridStats {
725 self.stats.with_tree_cycles(self.projection.tree_cycles())
727 }
728
729 pub fn text_stats(&self) -> TextStats {
731 self.text_cache.stats()
732 }
733}
734
735impl<D: LiveGridDelegate> EventEmitter<LiveGridEvent<KeyOf<D>, ColumnOf<D>>> for LiveGridState<D> {}
736
737impl<D: LiveGridDelegate> Focusable for LiveGridState<D> {
738 fn focus_handle(&self, _: &App) -> FocusHandle {
739 self.focus_handle.clone()
740 }
741}
742
743impl<D: LiveGridDelegate> Render for LiveGridState<D> {
744 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
745 self.close_stale_menu(window, cx);
746 self.settle_row_drag(cx);
748 self.apply_scroll_request();
749 self.ensure_projection(cx);
750 let a11y = self.is_a11y_planned(window);
751 let header_tooltip = self.header_tooltip(window, cx);
752 let ghost_text = GhostTextStyle::capture(window);
754 let header = self.render_header(header_tooltip, &ghost_text, a11y, cx);
755 let group_panel = self.render_group_panel(window, cx);
757 let scrollbars = self.render_scrollbars();
758 let progress = self.render_progress_overlay(window, cx);
759 let menu = self.render_menu(window);
760 let column_chooser = self.render_column_chooser(window, cx);
762 let role = root_role(self.projection.is_grouped() || self.projection.is_tree());
767 let row_count = self.a11y_numbering().row_count();
768 let column_count = self.columns.visible_set().len();
769 let multiselectable = !self.selection.mode().is_single();
770 let theme = cx.theme();
771 let root = div()
772 .id("live-grid")
773 .test_support()
774 .track_focus(&self.focus_handle)
775 .role(role)
776 .when_some(self.accessibility_label.clone(), |this, label| {
777 this.aria_label(label)
778 })
779 .aria_row_count(row_count)
780 .aria_column_count(column_count)
781 .when(a11y && multiselectable, |this| {
784 this.a11y_synthetic_children(|builder| builder.parent_node().set_multiselectable())
785 })
786 .key_context(self.root_key_context())
787 .on_action(cx.listener(Self::on_autofit_columns))
788 .on_action(cx.listener(Self::on_autofit_column))
789 .on_action(cx.listener(Self::on_hide_column))
790 .on_action(cx.listener(Self::on_show_all_columns))
791 .on_action(cx.listener(Self::on_freeze_column_left))
792 .on_action(cx.listener(Self::on_freeze_column_right))
793 .on_action(cx.listener(Self::on_unfreeze_column))
794 .on_action(cx.listener(Self::on_move_column_left))
795 .on_action(cx.listener(Self::on_move_column_right))
796 .on_action(cx.listener(Self::on_reset_layout))
797 .on_action(cx.listener(Self::on_show_context_menu))
798 .on_action(cx.listener(Self::on_show_column_chooser))
799 .on_action(cx.listener(Self::on_sort_ascending))
800 .on_action(cx.listener(Self::on_sort_descending))
801 .on_action(cx.listener(Self::on_clear_sort))
802 .on_action(cx.listener(Self::on_clear_all_sorting))
803 .map(|this| Self::bind_selection_actions(this, cx))
804 .map(|this| Self::bind_disclosure_actions(this, cx))
806 .on_action(cx.listener(Self::on_copy))
808 .on_action(cx.listener(Self::on_copy_all))
809 .map(|this| Self::bind_group_panel(this, cx))
811 .map(|this| Self::bind_edit_actions(this, cx))
813 .map(|this| Self::bind_cell_actions(this, cx))
814 .map(|this| Self::bind_link_actions(this, cx))
815 .on_drag_move(cx.listener(Self::on_columns_drag_move))
817 .on_drag_move(cx.listener(Self::on_resize_drag_move))
818 .on_drop(cx.listener(Self::on_columns_drop))
819 .size_full()
820 .flex()
821 .flex_col()
822 .overflow_hidden()
823 .bg(theme.table)
824 .text_color(theme.foreground)
825 .children(group_panel)
827 .child(header)
828 .child(self.render_rows(GridBody::new(cx.entity()), ghost_text, cx))
830 .child(scrollbars)
831 .children(progress);
833 div()
838 .size_full()
839 .child(root)
840 .children(menu)
841 .children(column_chooser)
842 }
843}
844
845impl<D: LiveGridDelegate> LiveGridState<D> {
846 fn render_scrollbars(&self) -> impl IntoElement {
854 let geometry = self
855 .columns
856 .geometry(self.body_bounds.size.width, self.scroll_x);
857 let scrolling = geometry.zone(ColumnZone::Scrolling);
858 let (left, width) = (scrolling.x, scrolling.width);
859 let bands = self.body_bands();
860 let top_band = bands.band(PinnedEdge::Top).size.height;
861 let bottom_band = bands.band(PinnedEdge::Bottom).size.height;
862 div()
863 .absolute()
864 .top(self.body_top())
866 .left_0()
867 .right_0()
868 .bottom_0()
869 .child(
870 div()
871 .absolute()
872 .top(top_band)
873 .right_0()
874 .bottom(bottom_band)
875 .w(Scrollbar::width())
876 .child(
877 Scrollbar::vertical(&self.scroll_handle)
878 .viewport_from_layout()
879 .max_fps(60),
880 ),
881 )
882 .child(
883 div()
884 .absolute()
885 .left(left)
886 .bottom(bottom_band)
887 .w(width)
888 .h(Scrollbar::width())
889 .child(Scrollbar::horizontal(&self.scroll_handle).viewport_from_layout()),
890 )
891 }
892}