Skip to main content

gpui_kit_grid/state/
events.rs

1//! Events a grid emits.
2
3use std::{fmt, sync::Arc};
4
5use gpui_kit::{Modifiers, MouseButton};
6
7use super::{
8    drag::{DropSource, DropTarget},
9    edit::{EditCancelReason, EditId, EditOrigin},
10    menu::GridTarget,
11    selection::{FocusedCell, GridSelection},
12};
13use crate::{
14    engine::{GroupPath, SortDirection},
15    model::{ColumnSet, OwnedValue},
16};
17
18/// Why the selection or the focus changed, so a subscriber that sets the
19/// selection itself can avoid a feedback loop.
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21#[non_exhaustive]
22pub enum SelectionOrigin {
23    /// A click, a key press or a menu command.
24    User,
25    /// A call of the grid API, such as [`LiveGridState::select_key`].
26    ///
27    /// [`LiveGridState::select_key`]: super::LiveGridState::select_key
28    Programmatic,
29    /// The row was removed from the store, or a new data set replaced it.
30    DataRemoved,
31    /// The row left the projection (a filter, the quick filter); it is
32    /// still in the store.
33    Filtered,
34}
35
36/// One part of the column layout.
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38#[non_exhaustive]
39pub enum LayoutPart {
40    /// Column widths.
41    Width,
42    /// The display order of columns.
43    Order,
44    /// Which columns are shown.
45    Visibility,
46    /// Which zone (frozen left, scrolling, frozen right) columns are in.
47    Zone,
48}
49
50impl LayoutPart {
51    const ALL: [Self; 4] = [Self::Width, Self::Order, Self::Visibility, Self::Zone];
52
53    const fn bit(self) -> u8 {
54        1 << self as u8
55    }
56}
57
58/// A set of [`LayoutPart`]s.
59#[derive(Clone, Copy, Default, PartialEq, Eq)]
60pub struct LayoutParts(u8);
61
62impl LayoutParts {
63    /// No parts.
64    pub(crate) const EMPTY: Self = Self(0);
65
66    /// This set with `part` added.
67    pub(crate) const fn with(self, part: LayoutPart) -> Self {
68        Self(self.0 | part.bit())
69    }
70
71    /// Adds every part of `other`.
72    pub(crate) fn union_with(&mut self, other: Self) {
73        self.0 |= other.0;
74    }
75
76    /// Returns true if the set contains `part`.
77    pub fn contains(self, part: LayoutPart) -> bool {
78        self.0 & part.bit() != 0
79    }
80
81    /// Returns true if the set has no parts.
82    pub fn is_empty(self) -> bool {
83        self.0 == 0
84    }
85}
86
87impl From<LayoutPart> for LayoutParts {
88    fn from(part: LayoutPart) -> Self {
89        Self::EMPTY.with(part)
90    }
91}
92
93impl FromIterator<LayoutPart> for LayoutParts {
94    fn from_iter<I: IntoIterator<Item = LayoutPart>>(parts: I) -> Self {
95        parts
96            .into_iter()
97            .fold(Self::EMPTY, |parts, part| parts.with(part))
98    }
99}
100
101impl fmt::Debug for LayoutParts {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        f.debug_set()
104            .entries(LayoutPart::ALL.iter().filter(|part| self.contains(**part)))
105            .finish()
106    }
107}
108
109/// Who changed the column layout, so a subscriber can avoid feedback loops.
110#[derive(Clone, Copy, Debug, PartialEq, Eq)]
111#[non_exhaustive]
112pub enum LayoutOrigin {
113    /// A pointer gesture or an Action.
114    User,
115    /// A call of the grid API.
116    Programmatic,
117}
118
119/// Why the sort changed, so a subscriber that keeps the sort itself and
120/// calls `set_sort` can avoid a feedback loop.
121#[derive(Clone, Copy, Debug, PartialEq, Eq)]
122#[non_exhaustive]
123pub enum SortOrigin {
124    /// A header click or a menu command.
125    User,
126    /// `set_sort`, `set_view`, or a restored layout.
127    Programmatic,
128}
129
130/// Who changed the grouping or the expansion, so a subscriber that keeps
131/// them itself and calls `set_group_by` or `set_expanded` can avoid a
132/// feedback loop.
133#[derive(Clone, Copy, Debug, PartialEq, Eq)]
134#[non_exhaustive]
135pub enum GroupOrigin {
136    /// The group panel, a chevron, a key, a menu command or an Action.
137    User,
138    /// A call of the grid API (`set_group_by`, `set_view`,
139    /// `set_expand_depth`, `set_expanded`), or a restored layout.
140    Programmatic,
141}
142
143/// A group or a tree row to expand or collapse, by identity: a group by its
144/// path, a tree row by its key.
145#[derive(Clone, Debug, PartialEq, Eq)]
146#[non_exhaustive]
147pub enum ExpandTarget<K> {
148    /// A group.
149    Group(GroupPath),
150    /// A tree row.
151    Row(K),
152}
153
154/// Events of a [`LiveGridState`]: `K` is the row key, `C` the column.
155///
156/// [`LiveGridState`]: super::LiveGridState
157#[derive(Clone, Debug)]
158#[non_exhaustive]
159pub enum LiveGridEvent<K, C> {
160    /// The selection changed: at most once per action, before its
161    /// `FocusChange`.
162    SelectionChange {
163        /// The whole selection, by key; O(1) to clone. Commands use
164        /// `scoped_selection()` instead: this set may hold rows a filter
165        /// hides.
166        selection: GridSelection<K>,
167        /// Why it changed.
168        origin: SelectionOrigin,
169    },
170    /// The focused cell (the keyboard target) changed: at most once per
171    /// action, after its `SelectionChange`.
172    FocusChange {
173        /// The new focused cell; `None` when no row is left to focus.
174        cell: Option<FocusedCell<K, C>>,
175        /// Why it changed.
176        origin: SelectionOrigin,
177    },
178    /// Enter, Cmd/Ctrl+Enter or a double click on a selected row: the
179    /// application's primary command.
180    Confirm {
181        /// The scoped selection in display order; never empty.
182        keys: Arc<[K]>,
183        /// Cmd/Ctrl+Enter: the secondary command.
184        secondary: bool,
185    },
186    /// A click: a press and a release of one button on a cell, a pinned
187    /// cell, a header or the empty area, with no drag between them. Once per
188    /// click, when the button goes up, after the events the click caused
189    /// (`SelectionChange`, `FocusChange`, `SortChange`; the `Confirm` of a
190    /// double click comes at its second press, before the release).
191    ///
192    /// Not a click: a drag (drag-selection, column move, resize), a press on
193    /// a resize handle, a press that only closes the context menu, and a
194    /// press the [`ContextMenuGesture`] takes (the menu is its result).
195    ///
196    /// [`ContextMenuGesture`]: crate::ContextMenuGesture
197    Click {
198        /// What the press hit, captured when the button went down, by key
199        /// and column: a re-sort before the release does not retarget it. A
200        /// row removed from the store (or its band) before the release
201        /// emits no `Click`.
202        target: GridTarget<K, C>,
203        /// The button.
204        button: MouseButton,
205        /// The modifiers held when the button went down.
206        modifiers: Modifiers,
207        /// The press's place in a series: 2 for the second press of a
208        /// double click.
209        click_count: usize,
210    },
211    /// A new display order is shown: a rebuild the reorder policy applied
212    /// (a new sort, filter, quick filter, grouping or data set among them),
213    /// `apply_held_order`, a compaction after removals, or the lines laid
214    /// out again after an `ExpansionChange`. Once per order shown; not while
215    /// a newer order is held (`OrderHeldChange` reports that), and not when
216    /// only values (aggregates among them), pinned rows or the progress
217    /// change. A `flush` that waits for the order resolves after it.
218    ProjectionChange {
219        /// Data rows in the shown order (`Projection::row_count`): rows of
220        /// collapsed groups count, pinned rows do not.
221        row_count: usize,
222        /// The version of the store snapshot the shown order was built
223        /// from.
224        store_version: u64,
225    },
226    /// A newer order is ready but held by the [`ReorderPolicy`] (`held`), or
227    /// the held order was applied (`!held`). Drive a "refresh order" cue.
228    ///
229    /// [`ReorderPolicy`]: crate::policy::ReorderPolicy
230    OrderHeldChange {
231        /// Whether a newer order is waiting.
232        held: bool,
233    },
234    /// The column layout changed: once per finished gesture, Action or API
235    /// call, never while a gesture is in progress. Emitted after the state
236    /// changed, so a subscriber that reads the grid sees the new layout.
237    LayoutChange {
238        /// What changed.
239        parts: LayoutParts,
240        /// The columns that changed (every moved column for an order change),
241        /// by schema index: `C::all()[ix.get()]` is the column.
242        columns: ColumnSet,
243        /// Who changed it.
244        origin: LayoutOrigin,
245    },
246    /// The sort changed. Emitted at once, before the rebuild (after an
247    /// `OrderHeldChange { held: false }` if a held order was dropped); a
248    /// `ProjectionChange` follows when the new order is shown.
249    SortChange {
250        /// The new sort, most significant column first; empty when unsorted.
251        sort: Arc<[(C, SortDirection)]>,
252        /// Why it changed.
253        origin: SortOrigin,
254    },
255    /// The grouping or the tree changed: `group_by()` and `tree()` read the
256    /// new one. Emitted at once, before the rebuild (after an
257    /// `OrderHeldChange { held: false }` if a held order was dropped, and
258    /// after the `SortChange` of the same `set_view`); a `ProjectionChange`
259    /// follows when the new order is shown. A change of the aggregates
260    /// alone emits nothing: they are values, not the order.
261    GroupChange {
262        /// Who changed it.
263        origin: GroupOrigin,
264    },
265    /// Groups or tree rows were expanded or collapsed. Emitted at once,
266    /// before the lines are laid out again: `is_expanded` reads the new
267    /// state, and a `ProjectionChange` follows when the lines are shown.
268    ExpansionChange {
269        /// The one group or tree row toggled (with its descendants, for a
270        /// deep toggle); `None` for a new default depth, which clears every
271        /// toggle (expand all, collapse all, captions only).
272        target: Option<ExpandTarget<K>>,
273        /// Who changed it.
274        origin: GroupOrigin,
275    },
276    // Editing and dropping rows.
277    /// An editor opened on a cell (not for a commit with no field).
278    EditStart {
279        /// The row.
280        key: K,
281        /// The column.
282        column: C,
283        /// Who started the edit.
284        origin: EditOrigin,
285    },
286    /// A value to apply: write it where the rows come from, then answer
287    /// with `LiveGridState::resolve_edit(edit, result)`. Until then the
288    /// cell shows `value`. `base` is the value the edit started from,
289    /// `current` the store's value at the commit: they differ after a
290    /// concurrent change (a conflict).
291    EditCommit {
292        /// The commit, for `resolve_edit`.
293        edit: EditId,
294        /// The row.
295        key: K,
296        /// The column.
297        column: C,
298        /// The value to apply.
299        value: OwnedValue,
300        /// The cell's value when the edit started.
301        base: OwnedValue,
302        /// The cell's value in the store at the commit.
303        current: OwnedValue,
304    },
305    /// An editor closed without a commit.
306    EditCancel {
307        /// The row.
308        key: K,
309        /// The column.
310        column: C,
311        /// Why.
312        reason: EditCancelReason,
313        /// The draft as it parsed, if it did and differs from the value the
314        /// edit started from: a dropped edit to show or retry.
315        draft: Option<OwnedValue>,
316    },
317    /// Rows or an application value were dropped on the rows. Emitted by
318    /// the grid dropped on, once per drop, after the source grid's own
319    /// events of the gesture. The grid changes nothing: the application
320    /// moves, copies or links data.
321    Drop {
322        /// What was dropped.
323        source: DropSource<K>,
324        /// Where it landed.
325        target: DropTarget<K>,
326        /// The modifiers held at the drop; copy or move is the
327        /// application's choice (Option on macOS, Ctrl elsewhere usually
328        /// means copy).
329        modifiers: Modifiers,
330    },
331}