Skip to main content

gpui_kit_grid/state/
handle.rs

1//! A grid without its delegate type: what a toolbar, a status bar or a
2//! column chooser needs to read and command any grid by column id
3//! ([`LiveGridHandle`]), and what its events say without keys and columns
4//! ([`GridHandleEvent`]).
5//!
6//! The handle holds the typed entity behind an object-safe trait
7//! ([`GridOps`]): each method is one `read` or one `update` of the grid.
8//! Like the entity, it must not be used from inside an update of the same
9//! grid, a delegate hook included: the re-entrant borrow panics. Commands
10//! are `Programmatic`, as the typed API's are.
11
12use 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/// A grid without its delegate type: columns, layout, sort, grouping, quick
38/// filter, counts, the held order, copy and export, and its events, all by
39/// column id. What a toolbar, a status bar or a column chooser needs.
40///
41/// Clones refer to the same grid and keep it alive, as an `Entity` does;
42/// [`downgrade`](Self::downgrade) for a back reference. Each method is one
43/// read or one update of the grid: never call it from inside an update of
44/// the same grid, such as a delegate hook (the borrow panics). Edits are
45/// not here: they need row keys, the application's own type.
46///
47/// ```
48/// use gpui_kit::{App, IntoElement, ParentElement as _, div};
49/// use gpui_kit_grid::LiveGridHandle;
50///
51/// /// "120 of 4000 rows", whatever the grid's rows are.
52/// fn row_counts(grid: &LiveGridHandle, cx: &App) -> impl IntoElement {
53///     div().child(format!(
54///         "{} of {} rows",
55///         grid.row_count(cx),
56///         grid.total_row_count(cx)
57///     ))
58/// }
59/// ```
60#[derive(Clone)]
61pub struct LiveGridHandle {
62    ops: Rc<dyn GridOps>,
63}
64
65impl LiveGridHandle {
66    /// A handle of `grid`.
67    pub fn new<D: LiveGridDelegate>(grid: &Entity<LiveGridState<D>>) -> Self {
68        Self {
69            ops: Rc::new(Ops { grid: grid.clone() }),
70        }
71    }
72
73    /// The grid entity's id.
74    pub fn entity_id(&self) -> EntityId {
75        self.ops.entity_id()
76    }
77
78    /// A handle that does not keep the grid alive.
79    pub fn downgrade(&self) -> WeakLiveGridHandle {
80        self.ops.downgrade()
81    }
82
83    /// The grid's focus handle: focus it to give the grid the keyboard.
84    pub fn focus_handle(&self, cx: &App) -> FocusHandle {
85        self.ops.focus_handle(cx)
86    }
87
88    // Columns, by `ColumnId` text.
89
90    /// Every schema column in display order (left, scrolling, right zone),
91    /// hidden ones at their place. O(columns).
92    pub fn columns(&self, cx: &App) -> Vec<ColumnInfo> {
93        self.ops.columns(cx)
94    }
95
96    /// The schema column `id`; `None` if the schema has none.
97    pub fn column(&self, id: &str, cx: &App) -> Option<ColumnInfo> {
98        self.ops.column(id, cx)
99    }
100
101    /// Shows or hides the column `id`, as
102    /// [`LiveGridState::set_column_visible`] does. Returns false, changing
103    /// nothing, if the column is unknown or is the last visible one.
104    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    /// Shows every column, with one `LayoutChange`.
109    pub fn show_all_columns(&self, cx: &mut App) {
110        self.ops.show_all_columns(cx);
111    }
112
113    /// Moves the column `id` before the column `before` (`None`: to the end
114    /// of its zone), as [`LiveGridState::move_column`] does. Returns false,
115    /// changing nothing, if a column is unknown, the zones differ or the
116    /// move policy refuses.
117    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    /// Moves every column of `band` before the column `before` (`None`: to
122    /// the end of the zone), as [`LiveGridState::move_band`] does. Returns
123    /// false, changing nothing, if the band or the column is unknown, the
124    /// zones differ or the move policy refuses.
125    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    /// Moves the column `id` to `zone`, as
130    /// [`LiveGridState::set_column_zone`] does. Returns false if the column
131    /// is unknown.
132    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    /// Sets the width of the column `id`, within its limits. Returns false
137    /// if the column is unknown.
138    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    /// Fits every visible column to its header and painted rows, as
143    /// [`LiveGridState::autofit_columns`] does.
144    pub fn autofit_columns(&self, cx: &mut App) {
145        self.ops.autofit_columns(cx);
146    }
147
148    // Layout, sort, grouping, quick filter.
149
150    /// The view id of the grid's saved layouts, if it has one.
151    pub fn view_id(&self, cx: &App) -> Option<SharedString> {
152        self.ops.view_id(cx)
153    }
154
155    /// The current layout ([`LiveGridState::layout`]).
156    pub fn layout(&self, cx: &App) -> GridLayout {
157        self.ops.layout(cx)
158    }
159
160    /// Restores a saved layout ([`LiveGridState::set_layout`]).
161    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    /// Restores the default layout ([`LiveGridState::reset_layout`]).
170    pub fn reset_layout(&self, cx: &mut App) {
171        self.ops.reset_layout(cx);
172    }
173
174    /// The sort by column id, most significant first; empty when unsorted.
175    pub fn sort(&self, cx: &App) -> Vec<LayoutSort> {
176        self.ops.sort(cx)
177    }
178
179    /// Replaces the sort, as [`LiveGridState::set_sort`] does. Returns
180    /// false, changing nothing, if a column is unknown.
181    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    /// The grouping levels by id, the top level first: a column level by its
186    /// column's id, a `GroupKey` level by its own.
187    pub fn group_by(&self, cx: &App) -> Vec<LayoutGroup> {
188        self.ops.group_by(cx)
189    }
190
191    /// Replaces the grouping by column levels, as
192    /// [`LiveGridState::set_group_by`] does; no levels turn grouping off.
193    /// Returns false, changing nothing, if a column is unknown: a `GroupKey`
194    /// level needs the typed state.
195    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    /// How deep groups and tree rows are expanded by default.
205    pub fn expand_depth(&self, cx: &App) -> ExpandDepth {
206        self.ops.expand_depth(cx)
207    }
208
209    /// Sets the default depth and clears every toggle
210    /// ([`LiveGridState::set_expand_depth`]).
211    pub fn set_expand_depth(&self, depth: ExpandDepth, cx: &mut App) {
212        self.ops.set_expand_depth(depth, cx);
213    }
214
215    /// The quick filter text; empty when the filter is off.
216    pub fn quick_filter(&self, cx: &App) -> SharedString {
217        self.ops.quick_filter(cx)
218    }
219
220    /// Sets the quick filter text ([`LiveGridState::set_quick_filter`]).
221    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    // Counts and the order: O(1).
226
227    /// The data rows of the shown order (`Projection::row_count`): rows of
228    /// collapsed groups count, pinned rows do not.
229    pub fn row_count(&self, cx: &App) -> usize {
230        self.ops.row_count(cx)
231    }
232
233    /// The live rows of the shown snapshot: the "4000" of "120 of 4000".
234    pub fn total_row_count(&self, cx: &App) -> usize {
235        self.ops.total_row_count(cx)
236    }
237
238    /// The selected rows, those a filter hides included.
239    pub fn selected_count(&self, cx: &App) -> usize {
240        self.ops.selected_count(cx)
241    }
242
243    /// Returns true if a newer order is ready but held by the reorder
244    /// policy.
245    pub fn is_order_held(&self, cx: &App) -> bool {
246        self.ops.is_order_held(cx)
247    }
248
249    /// Shows the held order now.
250    pub fn apply_held_order(&self, cx: &mut App) {
251        self.ops.apply_held_order(cx);
252    }
253
254    /// Replaces the reorder policy, as [`LiveGridState::set_reorder_policy`]
255    /// does: a held order is offered to the new policy at once. A toolbar's
256    /// Live / Hold / Manual switch, without the delegate type.
257    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    // Commands.
262
263    /// Copies `scope` to the clipboard ([`LiveGridState::copy_scope`]).
264    /// Returns false, leaving the clipboard alone, if there is nothing to
265    /// copy.
266    pub fn copy_scope(&self, scope: ExportScope, cx: &mut App) -> bool {
267        self.ops.copy_scope(scope, cx)
268    }
269
270    /// Returns true if an export of `scope` would have nothing to write
271    /// ([`LiveGridState::is_export_empty`]).
272    pub fn is_export_empty(&self, scope: ExportScope, cx: &App) -> bool {
273        self.ops.is_export_empty(scope, cx)
274    }
275
276    /// Exports `scope` as CSV to a file at `path`, with a header line and
277    /// the grid's cell export ([`LiveGridState::export_to_path`]). Other
278    /// formats and requests go through the typed state. Not on the web.
279    #[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    // Observing.
291
292    /// Calls `on_event` with what each event of the grid says without keys
293    /// and columns ([`GridHandleEvent`]), until the subscription is
294    /// dropped. Events with keys (`Click`, `Confirm`, `FocusChange`, edits)
295    /// need the typed subscription.
296    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    /// Calls `on_notify` whenever the grid notifies, until the subscription
305    /// is dropped: for a status bar that reads the counts again.
306    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    // The column chooser.
311
312    /// Opens the grid's column chooser, as
313    /// [`LiveGridState::show_column_chooser`] does.
314    pub fn show_column_chooser(&self, window: &mut Window, cx: &mut App) {
315        self.ops.show_column_chooser(window, cx);
316    }
317
318    /// Returns true while the grid shows its column chooser.
319    pub fn is_column_chooser_open(&self, cx: &App) -> bool {
320        self.ops.is_column_chooser_open(cx)
321    }
322
323    /// The grid behind the handle, for the column chooser.
324    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/// A [`LiveGridHandle`] that does not keep the grid alive.
344#[derive(Clone)]
345pub struct WeakLiveGridHandle {
346    entity_id: EntityId,
347    upgrade: Rc<dyn Fn() -> Option<LiveGridHandle>>,
348}
349
350impl WeakLiveGridHandle {
351    /// The handle, if the grid is still alive.
352    pub fn upgrade(&self) -> Option<LiveGridHandle> {
353        (self.upgrade)()
354    }
355
356    /// The grid entity's id.
357    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/// What a `LiveGridEvent` says without keys and columns.
371#[derive(Clone, Debug, PartialEq, Eq)]
372#[non_exhaustive]
373pub enum GridHandleEvent {
374    /// The column layout changed.
375    LayoutChange {
376        /// What changed.
377        parts: LayoutParts,
378        /// Who changed it.
379        origin: LayoutOrigin,
380    },
381    /// The sort changed; [`LiveGridHandle::sort`] reads it.
382    SortChange {
383        /// Why it changed.
384        origin: SortOrigin,
385    },
386    /// The grouping or the tree changed.
387    GroupChange {
388        /// Who changed it.
389        origin: GroupOrigin,
390    },
391    /// Groups or tree rows were expanded or collapsed.
392    ExpansionChange {
393        /// Who changed it.
394        origin: GroupOrigin,
395    },
396    /// A new display order is shown.
397    ProjectionChange {
398        /// Data rows in the shown order.
399        row_count: usize,
400        /// The version of the store snapshot the order was built from.
401        store_version: u64,
402    },
403    /// The selection changed.
404    SelectionChange {
405        /// The selected rows, those a filter hides included.
406        count: usize,
407        /// Why it changed.
408        origin: SelectionOrigin,
409    },
410    /// A newer order is held (`held`), or the held order was applied.
411    OrderHeldChange {
412        /// Whether a newer order is waiting.
413        held: bool,
414    },
415}
416
417impl GridHandleEvent {
418    /// What `event` says without keys and columns; `None` for an event about
419    /// rows or cells.
420    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/// A schema column as the grid shows it now.
449#[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    /// The column's id.
469    pub fn id(&self) -> &ColumnId {
470        &self.id
471    }
472
473    /// The column's name: its header text, or the tooltip and accessible
474    /// name of a header that shows an icon alone.
475    pub fn title(&self) -> &SharedString {
476        &self.title
477    }
478
479    /// The header's icon (its asset path), if it shows one.
480    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    /// What the header shows.
488    pub fn header_label(&self) -> &HeaderLabel {
489        &self.header_label
490    }
491
492    /// The zone the column is in.
493    pub fn zone(&self) -> ColumnZone {
494        self.zone
495    }
496
497    /// Returns true if the column is shown.
498    pub fn is_visible(&self) -> bool {
499        self.visible
500    }
501
502    /// The column's width.
503    pub fn width(&self) -> Pixels {
504        self.width
505    }
506
507    /// The bands over the column, outermost first.
508    pub fn bands(&self) -> &[BandInfo] {
509        &self.bands
510    }
511
512    /// Returns true if the user may hide the column.
513    pub fn is_hideable(&self) -> bool {
514        self.hideable
515    }
516
517    /// Returns true if the column is internal (`GridColumn::with_internal`):
518    /// never shown, and left out of a column chooser.
519    pub fn is_internal(&self) -> bool {
520        self.internal
521    }
522
523    /// Returns true if the user can drag the column's header.
524    pub fn is_movable(&self) -> bool {
525        self.movable
526    }
527
528    /// Returns true if the user can resize the column.
529    pub fn is_resizable(&self) -> bool {
530        self.resizable
531    }
532
533    /// Returns true if a click on the column's header sorts by it.
534    pub fn is_sortable(&self) -> bool {
535        self.sortable
536    }
537
538    /// Returns true if the user can group by the column.
539    pub fn is_groupable(&self) -> bool {
540        self.groupable
541    }
542
543    /// The column's sort direction, if the view sorts by it.
544    pub fn sort(&self) -> Option<SortDirection> {
545        self.sort.map(|(direction, _)| direction)
546    }
547
548    /// The column's place in a multi-column sort, from 0 for the most
549    /// significant.
550    pub fn sort_priority(&self) -> Option<usize> {
551        self.sort.map(|(_, priority)| priority)
552    }
553}
554
555/// A header band over a column.
556#[derive(Clone, Debug, PartialEq, Eq)]
557pub struct BandInfo {
558    id: &'static str,
559    title: SharedString,
560}
561
562impl BandInfo {
563    /// The band's id (`ColumnBand::new`).
564    pub fn id(&self) -> &'static str {
565        self.id
566    }
567
568    /// The band's header text.
569    pub fn title(&self) -> &SharedString {
570        &self.title
571    }
572}
573
574impl<D: LiveGridDelegate> LiveGridState<D> {
575    /// A handle of this grid without its delegate type: for a toolbar, a
576    /// status bar or a column chooser.
577    pub fn handle(&self, cx: &Context<Self>) -> LiveGridHandle {
578        LiveGridHandle::new(&cx.entity())
579    }
580
581    /// The schema column whose id is `id`.
582    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    /// The column `ix` as the grid shows it now; `None` outside the schema.
591    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    /// The `'static` id of the band `band`, if a schema column is under it.
631    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
640/// The grid behind a handle: the typed entity, as an object-safe trait.
641pub(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    // The column chooser.
689    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
699/// [`GridOps`] of a grid with the delegate `D`.
700struct Ops<D: LiveGridDelegate> {
701    grid: Entity<LiveGridState<D>>,
702}
703
704impl<D: LiveGridDelegate> Ops<D> {
705    /// Runs `command` on the column `id`; false, changing nothing, if the
706    /// column is unknown.
707    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            // As `set_reorder_policy`, with the policy already boxed.
923            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        // An event about rows or cells has no untyped form.
1018        assert_eq!(
1019            event(LiveGridEvent::Confirm {
1020                keys: [1].into(),
1021                secondary: false
1022            }),
1023            None
1024        );
1025    }
1026}