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}