Skip to main content

gpui_kit_grid/
policy.rs

1//! Replaceable behaviors of the view (AGENTS.md, "Replaceable Behavior"):
2//! when a new display order is shown, where background work runs, how
3//! columns move, how autofit measures, how a header click changes the sort,
4//! which selected rows a command acts on, which lines take the selection
5//! and what a command over a line acts on, how a press or a key edits the
6//! selection, which press opens the context menu, which press expands a
7//! group caption or a tree row, which input opens a cell editor and how an
8//! edit behaves under live data.
9
10use std::{ops::Range, sync::Arc, time::Duration};
11
12use gpui_kit::{App, AppContext as _, Modifiers, MouseButton, Pixels, Task, px};
13use web_time::Instant;
14
15use crate::{
16    engine::{Branch, GroupInfo, Projection, SortDirection},
17    model::{ColumnIx, ColumnSet, GridColumns as _, GridRow, OwnedValue},
18    state::{
19        CellContent, ColumnZone, DropPlace, DropTarget, EditInput, GridSelection, RowDrag,
20        ScopedSelection, SelectionMode,
21    },
22    store::StoreSnapshot,
23};
24
25/// What a [`ReorderPolicy`] knows when a new display order is ready.
26#[derive(Clone, Copy, Debug)]
27pub struct ReorderContext {
28    now: Instant,
29    held_since: Instant,
30    pointer_inside: bool,
31    user_action: bool,
32    menu_open: bool,
33    tooltip_shown: bool,
34    // Editing, waiting commits, row drags and the column chooser.
35    editing: bool,
36    pending_edits: bool,
37    drag_over: bool,
38    column_chooser_open: bool,
39}
40
41impl ReorderContext {
42    pub(crate) fn new(
43        now: Instant,
44        held_since: Instant,
45        pointer_inside: bool,
46        user_action: bool,
47    ) -> Self {
48        Self {
49            now,
50            held_since,
51            pointer_inside,
52            user_action,
53            menu_open: false,
54            tooltip_shown: false,
55            editing: false,
56            pending_edits: false,
57            drag_over: false,
58            column_chooser_open: false,
59        }
60    }
61
62    /// This context with the grid's context menu open or closed.
63    pub(crate) fn with_menu_open(mut self, menu_open: bool) -> Self {
64        self.menu_open = menu_open;
65        self
66    }
67
68    /// This context with a cell tooltip shown or not.
69    pub(crate) fn with_tooltip_shown(mut self, tooltip_shown: bool) -> Self {
70        self.tooltip_shown = tooltip_shown;
71        self
72    }
73
74    /// This context with a cell editor open or not.
75    pub(crate) fn with_editing(mut self, editing: bool) -> Self {
76        self.editing = editing;
77        self
78    }
79
80    /// This context with commits waiting or not.
81    pub(crate) fn with_pending_edits(mut self, pending_edits: bool) -> Self {
82        self.pending_edits = pending_edits;
83        self
84    }
85
86    /// This context with the grid's column chooser open or not.
87    pub(crate) fn with_column_chooser_open(mut self, column_chooser_open: bool) -> Self {
88        self.column_chooser_open = column_chooser_open;
89        self
90    }
91
92    /// This context with a drag over the rows or not.
93    pub(crate) fn with_drag_over(mut self, drag_over: bool) -> Self {
94        self.drag_over = drag_over;
95        self
96    }
97
98    /// The current time of the store clock.
99    pub fn now(&self) -> Instant {
100        self.now
101    }
102
103    /// When the shown order became outdated (equals `now` for a fresh one).
104    pub fn held_since(&self) -> Instant {
105        self.held_since
106    }
107
108    /// Returns true if the pointer is over the grid body.
109    pub fn is_pointer_inside(&self) -> bool {
110        self.pointer_inside
111    }
112
113    /// Returns true if the new order comes from a user action (a sort click,
114    /// a new view, a new data set) rather than from streaming data.
115    pub fn is_user_action(&self) -> bool {
116        self.user_action
117    }
118
119    /// Returns true while the grid's context menu is open. The built-in
120    /// policies hold the order then, except for a user action: the rows of
121    /// the menu's target stay where they were.
122    pub fn is_menu_open(&self) -> bool {
123        self.menu_open
124    }
125
126    /// Returns true while a cell tooltip is shown: GPUI built it for the
127    /// hovered cell, which has not changed since, and no button was pressed.
128    /// The built-in policies ignore it, since a resting pointer would hold
129    /// the order without end; a policy of the application may hold on it.
130    pub fn is_tooltip_shown(&self) -> bool {
131        self.tooltip_shown
132    }
133
134    /// Returns true while a cell editor is open: the built-in policies then
135    /// hold the order, as for the context menu, unless the new order comes
136    /// from a user action.
137    pub fn is_editing(&self) -> bool {
138        self.editing
139    }
140
141    /// Returns true while commits wait for `LiveGridState::resolve_edit`
142    /// or for the store. The built-in policies ignore it; a policy may hold
143    /// on it.
144    pub fn has_pending_edits(&self) -> bool {
145        self.pending_edits
146    }
147
148    /// Returns true while a row drag is over this grid's rows. The built-in
149    /// policies ignore it.
150    pub fn is_drag_over(&self) -> bool {
151        self.drag_over
152    }
153
154    /// Returns true while the grid's column chooser is open. The built-in
155    /// policies ignore it.
156    pub fn is_column_chooser_open(&self) -> bool {
157        self.column_chooser_open
158    }
159}
160
161/// What to do with a new display order.
162#[derive(Clone, Copy, Debug, PartialEq, Eq)]
163pub enum ReorderDecision {
164    /// Show it now.
165    Apply,
166    /// Keep the current order until this instant, then ask again.
167    HoldUntil(Instant),
168    /// Keep the current order until the pointer leaves, the application calls
169    /// [`crate::LiveGridState::apply_held_order`], or a user action arrives.
170    Hold,
171}
172
173/// Decides when a new display order replaces the shown one, so rows do not
174/// jump under the user's pointer more than the application wants.
175///
176/// The default is [`LiveReorder`], the industry standard, as in AG Grid and
177/// DevExpress; [`HoldReorder`] and [`ManualReorder`] are the built-in
178/// alternatives. All three hold the order while the context menu or a cell
179/// editor is open ([`ReorderContext::is_menu_open`],
180/// [`ReorderContext::is_editing`]), unless the new order comes from a user
181/// action. None holds it for commits that wait
182/// ([`ReorderContext::has_pending_edits`]): a policy of the application may.
183pub trait ReorderPolicy: 'static {
184    /// Called when a new order is ready, when the pointer leaves the grid,
185    /// when the context menu or a cell editor closes, and when a
186    /// [`ReorderDecision::HoldUntil`] deadline passes.
187    fn decide(&self, context: &ReorderContext) -> ReorderDecision;
188}
189
190/// Show every new order at once (throttled by `min_rebuild_interval`); hold
191/// it only while the context menu or a cell editor is open: the row under
192/// the field stays where it is.
193#[derive(Clone, Copy, Debug, Default)]
194pub struct LiveReorder;
195
196impl ReorderPolicy for LiveReorder {
197    fn decide(&self, context: &ReorderContext) -> ReorderDecision {
198        if (context.is_menu_open() || context.is_editing()) && !context.is_user_action() {
199            ReorderDecision::Hold
200        } else {
201            ReorderDecision::Apply
202        }
203    }
204}
205
206/// Keep the order while the pointer is over the grid, at most `max`, and
207/// while the context menu or a cell editor is open, however long.
208#[derive(Clone, Copy, Debug)]
209pub struct HoldReorder {
210    max: Duration,
211}
212
213impl HoldReorder {
214    /// Holds for at most `max` while the pointer is over the grid.
215    pub fn new(max: Duration) -> Self {
216        Self { max }
217    }
218}
219
220impl Default for HoldReorder {
221    fn default() -> Self {
222        Self::new(Duration::from_secs(3))
223    }
224}
225
226impl ReorderPolicy for HoldReorder {
227    fn decide(&self, context: &ReorderContext) -> ReorderDecision {
228        if context.is_user_action() {
229            return ReorderDecision::Apply;
230        }
231        // The menu and the editor close by an explicit act, so this hold has
232        // an end.
233        if context.is_menu_open() || context.is_editing() {
234            return ReorderDecision::Hold;
235        }
236        if !context.is_pointer_inside() {
237            return ReorderDecision::Apply;
238        }
239        let deadline = context.held_since() + self.max;
240        if context.now() >= deadline {
241            ReorderDecision::Apply
242        } else {
243            ReorderDecision::HoldUntil(deadline)
244        }
245    }
246}
247
248/// Keep the order until the application applies it (for example from a
249/// "Refresh order" button); user actions still apply at once. An open
250/// context menu changes nothing: the order is held anyway.
251#[derive(Clone, Copy, Debug, Default)]
252pub struct ManualReorder;
253
254impl ReorderPolicy for ManualReorder {
255    fn decide(&self, context: &ReorderContext) -> ReorderDecision {
256        if context.is_user_action() {
257            ReorderDecision::Apply
258        } else {
259            ReorderDecision::Hold
260        }
261    }
262}
263
264/// Runs the view's background jobs (projection rebuilds and compactions).
265///
266/// The default, [`BackgroundRunner`], uses GPUI's background executor.
267/// [`InlineRunner`] runs jobs on the calling thread, for platforms without
268/// background threads (WebAssembly without `SharedArrayBuffer`).
269pub trait TaskRunner: 'static {
270    /// Runs `job`; the returned task completes when it is done.
271    fn run(&self, job: Box<dyn FnOnce() + Send>, cx: &App) -> Task<()>;
272}
273
274/// Runs jobs on GPUI's background executor.
275#[derive(Clone, Copy, Debug, Default)]
276pub struct BackgroundRunner;
277
278impl TaskRunner for BackgroundRunner {
279    fn run(&self, job: Box<dyn FnOnce() + Send>, cx: &App) -> Task<()> {
280        cx.background_spawn(async move { job() })
281    }
282}
283
284/// Runs jobs at once on the calling (UI) thread.
285#[derive(Clone, Copy, Debug, Default)]
286pub struct InlineRunner;
287
288impl TaskRunner for InlineRunner {
289    fn run(&self, job: Box<dyn FnOnce() + Send>, _: &App) -> Task<()> {
290        job();
291        Task::ready(())
292    }
293}
294
295/// One zone of the column layout, as a [`ColumnMovePolicy`] sees it.
296pub struct ColumnMoveContext<'a> {
297    zone: ColumnZone,
298    columns: &'a [ColumnIx],
299    visible: ColumnSet,
300    bands: &'a [Box<[&'static str]>],
301}
302
303impl<'a> ColumnMoveContext<'a> {
304    pub(crate) fn new(
305        zone: ColumnZone,
306        columns: &'a [ColumnIx],
307        visible: ColumnSet,
308        bands: &'a [Box<[&'static str]>],
309    ) -> Self {
310        Self {
311            zone,
312            columns,
313            visible,
314            bands,
315        }
316    }
317
318    /// The zone.
319    pub fn zone(&self) -> ColumnZone {
320        self.zone
321    }
322
323    /// The zone's columns in display order, hidden ones included.
324    pub fn columns(&self) -> &'a [ColumnIx] {
325        self.columns
326    }
327
328    /// Returns true if `column` is shown.
329    pub fn is_visible(&self, column: ColumnIx) -> bool {
330        self.visible.contains(column)
331    }
332
333    /// Ids of the bands that contain `column`, outermost first.
334    pub fn bands(&self, column: ColumnIx) -> &'a [&'static str] {
335        self.bands.get(column.get()).map_or(&[], |bands| bands)
336    }
337}
338
339/// The whole column order, as [`ColumnMovePolicy::normalize`] may change
340/// it: it can only reorder columns and change their zones. The grid then
341/// sorts the order by zone, keeping the order within each zone.
342pub struct ColumnOrder<'a> {
343    order: &'a mut Vec<ColumnIx>,
344    zones: &'a mut [ColumnZone],
345    bands: &'a [Box<[&'static str]>],
346}
347
348impl<'a> ColumnOrder<'a> {
349    pub(crate) fn new(
350        order: &'a mut Vec<ColumnIx>,
351        zones: &'a mut [ColumnZone],
352        bands: &'a [Box<[&'static str]>],
353    ) -> Self {
354        Self {
355            order,
356            zones,
357            bands,
358        }
359    }
360
361    /// Every column of the schema in display order, hidden ones included.
362    pub fn columns(&self) -> &[ColumnIx] {
363        self.order
364    }
365
366    /// The zone of `column`.
367    pub fn zone(&self, column: ColumnIx) -> ColumnZone {
368        self.zones.get(column.get()).copied().unwrap_or_default()
369    }
370
371    /// Ids of the bands that contain `column`, outermost first.
372    pub fn bands(&self, column: ColumnIx) -> &'a [&'static str] {
373        self.bands.get(column.get()).map_or(&[], |bands| bands)
374    }
375
376    /// Moves `column` to `zone`.
377    pub fn set_zone(&mut self, column: ColumnIx, zone: ColumnZone) {
378        if let Some(slot) = self.zones.get_mut(column.get()) {
379            *slot = zone;
380        }
381    }
382
383    /// Replaces the order. Returns false, changing nothing, unless `columns`
384    /// is a permutation of [`Self::columns`].
385    pub fn reorder(&mut self, columns: &[ColumnIx]) -> bool {
386        let set = |columns: &[ColumnIx]| {
387            columns.iter().fold(ColumnSet::EMPTY, |mut set, column| {
388                set.insert(*column);
389                set
390            })
391        };
392        if columns.len() != self.order.len() || set(columns) != set(self.order) {
393            return false;
394        }
395        self.order.clear();
396        self.order.extend_from_slice(columns);
397        true
398    }
399}
400
401/// Decides which columns move together and where they may go, for header
402/// drags, `move_column`, `move_band`, zone changes and the move Actions.
403///
404/// The default is [`BandedMove`]: a [`ColumnBand`](crate::ColumnBand) stays
405/// together, as in DevExpress, Syncfusion, Kendo and Infragistics.
406/// [`FreeMove`] lets a column leave its band, as AG Grid does.
407pub trait ColumnMovePolicy: 'static {
408    /// Whether `block` (a range of `context.columns()`) may be inserted at
409    /// `gap` (`0..=columns().len()`, counted before the block is removed).
410    fn is_allowed(&self, context: &ColumnMoveContext<'_>, block: Range<usize>, gap: usize) -> bool;
411
412    /// The columns that change zone together with `column`: a range of
413    /// `context.columns()` that contains it.
414    fn zone_block(&self, context: &ColumnMoveContext<'_>, column: ColumnIx) -> Range<usize>;
415
416    /// Restores the policy's invariants after the schema is built or a layout
417    /// is restored. Stable: a valid order stays as it is.
418    fn normalize(&self, _order: &mut ColumnOrder<'_>) {}
419}
420
421/// A band stays together: a column moves only among the children of its
422/// innermost band, a band moves as a whole, and a zone change takes the
423/// whole top-level band.
424#[derive(Clone, Copy, Debug, Default)]
425pub struct BandedMove;
426
427/// A child of a band (or of a zone): a nested band or a column.
428#[derive(Clone, Copy, PartialEq, Eq)]
429enum BandChild {
430    Band(&'static str),
431    Column(ColumnIx),
432}
433
434fn band_child(context: &ColumnMoveContext<'_>, column: ColumnIx, level: usize) -> BandChild {
435    match context.bands(column).get(level) {
436        Some(band) => BandChild::Band(band),
437        None => BandChild::Column(column),
438    }
439}
440
441/// The range of `columns` whose bands at `level` are `band`, from the first
442/// to the last of them.
443fn band_span(
444    context: &ColumnMoveContext<'_>,
445    level: usize,
446    band: &'static str,
447) -> Option<Range<usize>> {
448    let columns = context.columns();
449    let is_member = |column: &ColumnIx| context.bands(*column).get(level) == Some(&band);
450    let start = columns.iter().position(is_member)?;
451    let end = columns.iter().rposition(is_member)? + 1;
452    Some(start..end)
453}
454
455impl ColumnMovePolicy for BandedMove {
456    fn is_allowed(&self, context: &ColumnMoveContext<'_>, block: Range<usize>, gap: usize) -> bool {
457        let columns = context.columns();
458        if block.is_empty() || block.end > columns.len() || gap > columns.len() {
459            return false;
460        }
461        // The bands every column of the block is in.
462        let mut common = context.bands(columns[block.start]);
463        for column in &columns[block.clone()] {
464            let bands = context.bands(*column);
465            let shared = common.iter().zip(bands).take_while(|(a, b)| a == b).count();
466            common = &common[..shared];
467        }
468        // A block that is a whole band moves among that band's siblings (and
469        // a band that is all of its parent band, among the parent's).
470        while let Some((&deepest, outer)) = common.split_last()
471            && band_span(context, outer.len(), deepest)
472                .is_some_and(|span| block.start <= span.start && span.end <= block.end)
473        {
474            common = outer;
475        }
476        let level = common.len();
477        let parent = match common.last() {
478            Some(&band) => match band_span(context, level - 1, band) {
479                Some(span) => span,
480                None => return false,
481            },
482            None => 0..columns.len(),
483        };
484        if gap < parent.start || gap > parent.end {
485            return false;
486        }
487        gap == parent.start
488            || gap == parent.end
489            || band_child(context, columns[gap - 1], level)
490                != band_child(context, columns[gap], level)
491    }
492
493    fn zone_block(&self, context: &ColumnMoveContext<'_>, column: ColumnIx) -> Range<usize> {
494        let columns = context.columns();
495        let Some(position) = columns.iter().position(|c| *c == column) else {
496            return 0..0;
497        };
498        match context.bands(column).first() {
499            Some(&top) => band_span(context, 0, top).unwrap_or(position..position + 1),
500            None => position..position + 1,
501        }
502    }
503
504    fn normalize(&self, order: &mut ColumnOrder<'_>) {
505        let columns = order.columns().to_vec();
506        // Every top-level band takes the zone of its first column.
507        let mut zones: Vec<(&'static str, ColumnZone)> = Vec::new();
508        for &column in &columns {
509            let Some(&top) = order.bands(column).first() else {
510                continue;
511            };
512            match zones.iter().find(|(band, _)| *band == top) {
513                Some(&(_, zone)) => order.set_zone(column, zone),
514                None => zones.push((top, order.zone(column))),
515            }
516        }
517        let gathered = gather(&columns, 0, order);
518        order.reorder(&gathered);
519    }
520}
521
522/// Gathers every band at `level` into one run at the place of its first
523/// column, keeping the order inside it; then the bands one level deeper.
524fn gather(columns: &[ColumnIx], level: usize, order: &ColumnOrder<'_>) -> Vec<ColumnIx> {
525    let mut groups: Vec<(Option<&'static str>, Vec<ColumnIx>)> = Vec::new();
526    for &column in columns {
527        match order.bands(column).get(level) {
528            Some(&band) => match groups.iter_mut().find(|(key, _)| *key == Some(band)) {
529                Some((_, members)) => members.push(column),
530                None => groups.push((Some(band), vec![column])),
531            },
532            None => groups.push((None, vec![column])),
533        }
534    }
535    groups
536        .into_iter()
537        .flat_map(|(key, members)| match key {
538            Some(_) => gather(&members, level + 1, order),
539            None => members,
540        })
541        .collect()
542}
543
544/// Any column moves anywhere in its zone, out of its band too: a band then
545/// shows one header cell per run of its columns, as AG Grid does.
546#[derive(Clone, Copy, Debug, Default)]
547pub struct FreeMove;
548
549impl ColumnMovePolicy for FreeMove {
550    fn is_allowed(&self, context: &ColumnMoveContext<'_>, block: Range<usize>, gap: usize) -> bool {
551        !block.is_empty() && block.end <= context.columns().len() && gap <= context.columns().len()
552    }
553
554    fn zone_block(&self, context: &ColumnMoveContext<'_>, column: ColumnIx) -> Range<usize> {
555        context
556            .columns()
557            .iter()
558            .position(|c| *c == column)
559            .map_or(0..0, |position| position..position + 1)
560    }
561}
562
563/// What an [`AutofitPolicy`] can measure for one column. Widths are text
564/// widths, without cell padding.
565pub struct AutofitContext<'a> {
566    header_width: Pixels,
567    pinned_width: Pixels,
568    // The texts of the painted aggregates.
569    summary_width: Pixels,
570    painted_rows: Range<usize>,
571    row_count: usize,
572    measure: &'a mut dyn FnMut(usize) -> Option<Pixels>,
573}
574
575impl<'a> AutofitContext<'a> {
576    pub(crate) fn new(
577        header_width: Pixels,
578        painted_rows: Range<usize>,
579        row_count: usize,
580        measure: &'a mut dyn FnMut(usize) -> Option<Pixels>,
581    ) -> Self {
582        Self {
583            header_width,
584            pinned_width: Pixels::ZERO,
585            summary_width: Pixels::ZERO,
586            painted_rows,
587            row_count,
588            measure,
589        }
590    }
591
592    /// Sets the widest text of the column in the painted pinned rows.
593    pub(crate) fn with_pinned_width(mut self, width: Pixels) -> Self {
594        self.pinned_width = width;
595        self
596    }
597
598    /// The header title plus the sort indicator lane; a header icon and its
599    /// gap before the title, or the icon alone in place of it.
600    pub fn header_width(&self) -> Pixels {
601        self.header_width
602    }
603
604    /// The widest text of the column in the pinned rows painted in this
605    /// frame, both bands; zero without pinned rows.
606    pub fn pinned_width(&self) -> Pixels {
607        self.pinned_width
608    }
609
610    /// Sets the widest aggregate text of the column on the summary lines
611    /// painted in this frame.
612    pub(crate) fn with_summary_width(mut self, width: Pixels) -> Self {
613        self.summary_width = width;
614        self
615    }
616
617    /// The widest aggregate text of the column in the group captions, group
618    /// footers and totals row painted in this frame, the totals label
619    /// "Total" in its column included, each in the weight it is painted
620    /// in; zero without them. A caption's own text, which runs across
621    /// columns, is not part of it.
622    pub fn summary_width(&self) -> Pixels {
623        self.summary_width
624    }
625
626    /// Display rows painted in this frame.
627    pub fn painted_rows(&self) -> Range<usize> {
628        self.painted_rows.clone()
629    }
630
631    /// Rows in the projection.
632    pub fn row_count(&self) -> usize {
633        self.row_count
634    }
635
636    /// Text width of the column's cell in a display row; `None` for a
637    /// removed row or one past the end.
638    pub fn measure_row(&mut self, display_ix: usize) -> Option<Pixels> {
639        (self.measure)(display_ix)
640    }
641}
642
643/// Chooses the text width a column needs when it is autofitted (a double
644/// click on its resize handle, `autofit_column`, `autofit_columns`). The
645/// grid adds the cell padding and clamps to the column's limits.
646///
647/// Called once per column when the fit is applied, never per frame.
648pub trait AutofitPolicy: 'static {
649    /// The text width the column needs.
650    fn content_width(&self, context: &mut AutofitContext<'_>) -> Pixels;
651}
652
653/// Fits the header title, the pinned rows, the aggregates and the data rows
654/// painted in the frame of the fit; rows off screen are not measured, so the
655/// cost does not grow with the row count (AG Grid does the same).
656#[derive(Clone, Copy, Debug, Default)]
657pub struct VisibleRowsAutofit;
658
659impl AutofitPolicy for VisibleRowsAutofit {
660    fn content_width(&self, context: &mut AutofitContext<'_>) -> Pixels {
661        let floor = context
662            .header_width()
663            .max(context.pinned_width())
664            .max(context.summary_width());
665        context
666            .painted_rows()
667            .filter_map(|display_ix| context.measure_row(display_ix))
668            .fold(floor, |widest, width| widest.max(width))
669    }
670}
671
672/// What a [`SortCycle`] knows about a header click.
673#[derive(Clone, Copy, Debug)]
674pub struct SortCycleContext<'a> {
675    sort: &'a [(ColumnIx, SortDirection)],
676    column: ColumnIx,
677    modifiers: Modifiers,
678}
679
680impl<'a> SortCycleContext<'a> {
681    pub(crate) fn new(
682        sort: &'a [(ColumnIx, SortDirection)],
683        column: ColumnIx,
684        modifiers: Modifiers,
685    ) -> Self {
686        Self {
687            sort,
688            column,
689            modifiers,
690        }
691    }
692
693    /// The sort before the click, most significant column first.
694    pub fn sort(&self) -> &'a [(ColumnIx, SortDirection)] {
695        self.sort
696    }
697
698    /// The column whose header was clicked.
699    pub fn column(&self) -> ColumnIx {
700        self.column
701    }
702
703    /// The modifier keys held during the click.
704    pub fn modifiers(&self) -> Modifiers {
705        self.modifiers
706    }
707
708    /// The direction of the clicked column in the sort, if it is sorted.
709    pub fn direction(&self) -> Option<SortDirection> {
710        self.sort
711            .iter()
712            .find(|(column, _)| *column == self.column)
713            .map(|(_, direction)| *direction)
714    }
715}
716
717/// How a header click changes the sort.
718///
719/// The default is [`StandardSortCycle`]: ascending, descending, unsorted;
720/// Shift adds the column to a multi-column sort. Called once per click on
721/// the UI thread, and only for a column with
722/// [`GridColumn::is_sortable`](crate::GridColumn::is_sortable). The grid
723/// drops repeated columns of the result (the first one stays) and columns
724/// outside the schema's column type.
725pub trait SortCycle: 'static {
726    /// The sort after a click on `context.column()`, most significant column
727    /// first; empty for the insertion order.
728    fn next_sort(&self, context: &SortCycleContext<'_>) -> Vec<(ColumnIx, SortDirection)>;
729}
730
731/// Ascending, then descending, then unsorted (the insertion order), as in AG
732/// Grid, Syncfusion and MUI X.
733///
734/// A click without Shift steps the clicked column from its own direction and
735/// drops every other column, even from a multi-column sort. A Shift-click
736/// adds the column as the least significant one, flips it in place, then
737/// removes it; the others keep their order. Shift on every platform: on
738/// macOS Cmd-click means "add to the selection", not "add to the sort".
739#[derive(Clone, Copy, Debug, Default)]
740pub struct StandardSortCycle;
741
742impl SortCycle for StandardSortCycle {
743    fn next_sort(&self, context: &SortCycleContext<'_>) -> Vec<(ColumnIx, SortDirection)> {
744        let column = context.column();
745        let direction = context.direction();
746        if !context.modifiers().shift {
747            return match direction {
748                None => vec![(column, SortDirection::Ascending)],
749                Some(SortDirection::Ascending) => vec![(column, SortDirection::Descending)],
750                Some(SortDirection::Descending) => Vec::new(),
751            };
752        }
753        let mut sort = context.sort().to_vec();
754        match direction {
755            None => sort.push((column, SortDirection::Ascending)),
756            Some(SortDirection::Ascending) => {
757                for (sorted, direction) in &mut sort {
758                    if *sorted == column {
759                        *direction = SortDirection::Descending;
760                    }
761                }
762            }
763            Some(SortDirection::Descending) => sort.retain(|(sorted, _)| *sorted != column),
764        }
765        sort
766    }
767}
768
769/// What a [`SelectionScope`] knows when a command asks what it acts on.
770pub struct SelectionScopeContext<'a, R: GridRow> {
771    selection: &'a GridSelection<R::Key>,
772    projection: &'a Projection,
773    snapshot: &'a StoreSnapshot<R>,
774    mode: SelectionMode,
775}
776
777impl<'a, R: GridRow> SelectionScopeContext<'a, R> {
778    pub(crate) fn new(
779        selection: &'a GridSelection<R::Key>,
780        projection: &'a Projection,
781        snapshot: &'a StoreSnapshot<R>,
782        mode: SelectionMode,
783    ) -> Self {
784        Self {
785            selection,
786            projection,
787            snapshot,
788            mode,
789        }
790    }
791
792    /// The whole selection, by key; it may hold rows a filter hides.
793    pub fn selection(&self) -> &'a GridSelection<R::Key> {
794        self.selection
795    }
796
797    /// The projection on screen (not one being built or held back).
798    pub fn projection(&self) -> &'a Projection {
799        self.projection
800    }
801
802    /// The snapshot the screen shows.
803    pub fn snapshot(&self) -> &'a StoreSnapshot<R> {
804        self.snapshot
805    }
806
807    /// The selection mode.
808    pub fn mode(&self) -> SelectionMode {
809        self.mode
810    }
811}
812
813/// Which selected rows and cells a command acts on: Enter, a double click,
814/// the primary menu command, copying. Called once per command on the UI
815/// thread.
816///
817/// The default is [`ProjectionScope`]: the selection within the rows on
818/// screen. [`StoreScope`] also takes selected rows a filter hides.
819pub trait SelectionScope<R: GridRow>: 'static {
820    /// The rows and cells, in the order the command sees them.
821    fn resolve(&self, context: &SelectionScopeContext<'_, R>) -> ScopedSelection<R::Key>;
822}
823
824/// The selection within the projection on screen, in its order: rows a
825/// filter hides and rows removed since are left out; selected rows of a
826/// collapsed group are in. The default.
827#[derive(Clone, Copy, Debug, Default)]
828pub struct ProjectionScope;
829
830/// The selected rows `projection` passes, in its order (`rows()`): a row
831/// of a collapsed group too. One pass over the projection when most of it
832/// is selected, else a lookup per selected row and a sort by position.
833fn shown_selection<R: GridRow>(context: &SelectionScopeContext<'_, R>) -> Vec<(R::Key, ColumnSet)> {
834    let (selection, projection, snapshot) = (
835        context.selection(),
836        context.projection(),
837        context.snapshot(),
838    );
839    if selection.is_empty() {
840        return Vec::new();
841    }
842    if selection.len() > projection.rows().len() / 8 {
843        return projection
844            .rows()
845            .iter()
846            .filter_map(|id| snapshot.row(*id))
847            .filter_map(|row| {
848                let columns = selection.columns(row.key());
849                (!columns.is_empty()).then(|| (row.key().clone(), columns))
850            })
851            .collect();
852    }
853    let mut shown: Vec<(usize, R::Key, ColumnSet)> = selection
854        .iter()
855        .filter_map(|key| {
856            let order_ix = projection.order_ix(snapshot.row_id(key)?)?;
857            Some((order_ix, key.clone(), selection.columns(key)))
858        })
859        .collect();
860    shown.sort_unstable_by_key(|(order_ix, _, _)| *order_ix);
861    shown
862        .into_iter()
863        .map(|(_, key, columns)| (key, columns))
864        .collect()
865}
866
867impl<R: GridRow> SelectionScope<R> for ProjectionScope {
868    fn resolve(&self, context: &SelectionScopeContext<'_, R>) -> ScopedSelection<R::Key> {
869        shown_selection(context).into_iter().collect()
870    }
871}
872
873/// The selection within the store: the rows on screen in display order,
874/// then selected rows a filter hides, in insertion order. Removed rows never
875/// are.
876#[derive(Clone, Copy, Debug, Default)]
877pub struct StoreScope;
878
879impl<R: GridRow> SelectionScope<R> for StoreScope {
880    fn resolve(&self, context: &SelectionScopeContext<'_, R>) -> ScopedSelection<R::Key> {
881        let (selection, projection, snapshot) = (
882            context.selection(),
883            context.projection(),
884            context.snapshot(),
885        );
886        let mut hidden: Vec<(u64, R::Key, ColumnSet)> = selection
887            .iter()
888            .filter_map(|key| {
889                let id = snapshot.row_id(key)?;
890                (!projection.contains(id))
891                    .then(|| (snapshot.seq(id), key.clone(), selection.columns(key)))
892            })
893            .collect();
894        hidden.sort_unstable_by_key(|(seq, _, _)| *seq);
895        shown_selection(context)
896            .into_iter()
897            .chain(hidden.into_iter().map(|(_, key, columns)| (key, columns)))
898            .collect()
899    }
900}
901
902/// A line the selection may take, as a [`SelectionPolicy`] sees it.
903#[non_exhaustive]
904pub enum SelectableLine<'a, R: GridRow> {
905    /// A data row; `branch` is set for a tree row with shown children.
906    Row {
907        /// The row.
908        row: &'a R,
909        /// The row's children, in a tree.
910        branch: Option<Branch>,
911    },
912    /// A group caption.
913    Caption {
914        /// The group, in the projection on screen.
915        group: GroupInfo<'a>,
916    },
917}
918
919/// What [`SelectionPolicy::line_scope`] knows: the target line of a
920/// command in the projection on screen, the snapshot it is drawn from and
921/// the selection mode.
922pub struct LineScopeContext<'a, R: GridRow> {
923    line: SelectableLine<'a, R>,
924    /// The live rows under the line: a range of `Projection::rows`.
925    under: Range<usize>,
926    projection: &'a Projection,
927    snapshot: &'a StoreSnapshot<R>,
928    mode: SelectionMode,
929}
930
931impl<'a, R: GridRow> LineScopeContext<'a, R> {
932    pub(crate) fn new(
933        line: SelectableLine<'a, R>,
934        under: Range<usize>,
935        projection: &'a Projection,
936        snapshot: &'a StoreSnapshot<R>,
937        mode: SelectionMode,
938    ) -> Self {
939        Self {
940            line,
941            under,
942            projection,
943            snapshot,
944            mode,
945        }
946    }
947
948    /// The target line.
949    pub fn line(&self) -> &SelectableLine<'a, R> {
950        &self.line
951    }
952
953    /// The live rows under the line, in the order of `Projection::rows`:
954    /// every row of a caption's group, its collapsed subgroups' too; the
955    /// descendants of a tree row; none under any other row. Rows removed
956    /// since the projection was built are left out.
957    pub fn rows(&self) -> impl Iterator<Item = &'a Arc<R>> + 'a {
958        let snapshot = self.snapshot;
959        self.projection.rows()[self.under.clone()]
960            .iter()
961            .filter_map(move |id| snapshot.row(*id))
962    }
963
964    /// The rows of [`Self::rows`] as lines, each with its own children in
965    /// a tree: what [`SelectionPolicy::is_selectable`] is asked about them.
966    pub fn lines(&self) -> impl Iterator<Item = SelectableLine<'a, R>> + 'a {
967        let (projection, snapshot) = (self.projection, self.snapshot);
968        projection.rows()[self.under.clone()]
969            .iter()
970            .filter_map(move |id| {
971                Some(SelectableLine::Row {
972                    row: &**snapshot.row(*id)?,
973                    branch: projection.branch(*id),
974                })
975            })
976    }
977
978    /// The projection on screen.
979    pub fn projection(&self) -> &'a Projection {
980        self.projection
981    }
982
983    /// The snapshot the screen shows.
984    pub fn snapshot(&self) -> &'a StoreSnapshot<R> {
985        self.snapshot
986    }
987
988    /// The selection mode.
989    pub fn mode(&self) -> SelectionMode {
990        self.mode
991    }
992}
993
994/// Which lines can be selected, and what a command over a line acts on: a
995/// group caption, or a tree row with children.
996///
997/// `is_selectable` is asked per line by Cmd/Ctrl+A, Shift ranges and
998/// drags; `line_scope` once per command, for its target: the focused line
999/// for Enter and Cmd/Ctrl+C, the menu's target, a double-clicked row. Both
1000/// run on the UI thread.
1001///
1002/// The default is [`FocusCaptions`]: tree rows are data rows and take the
1003/// selection, as in AG Grid's tree data and DevExpress TreeList. Built in
1004/// too, [`FocusBranches`]: tree rows with children take the focus but not
1005/// the selection, and a command over one acts on its subtree's leaves.
1006/// Another plausible one: selectable captions whose selection takes their
1007/// group's rows (AG Grid's `groupSelectsChildren`).
1008pub trait SelectionPolicy<R: GridRow>: 'static {
1009    /// Returns true if `line` can be selected. A selectable caption adds
1010    /// its group's live rows to the selection; a selectable row itself.
1011    fn is_selectable(&self, line: &SelectableLine<'_, R>) -> bool;
1012
1013    /// The scope of a command whose target is `context.line()`; `None`:
1014    /// the command acts on `scoped_selection()`.
1015    fn line_scope(&self, context: &LineScopeContext<'_, R>) -> Option<ScopedSelection<R::Key>>;
1016}
1017
1018/// Captions take the focus, never the selection; a command over a caption
1019/// acts on every live row of its group, collapsed subgroups included, in
1020/// the order of `Projection::rows`, with every column. Data rows, tree rows
1021/// with children included, are selectable, and a command over one acts on
1022/// `scoped_selection()`. The default.
1023#[derive(Clone, Copy, Debug, Default)]
1024pub struct FocusCaptions;
1025
1026impl<R: GridRow> SelectionPolicy<R> for FocusCaptions {
1027    fn is_selectable(&self, line: &SelectableLine<'_, R>) -> bool {
1028        match line {
1029            SelectableLine::Row { .. } => true,
1030            SelectableLine::Caption { .. } => false,
1031        }
1032    }
1033
1034    fn line_scope(&self, context: &LineScopeContext<'_, R>) -> Option<ScopedSelection<R::Key>> {
1035        match context.line() {
1036            SelectableLine::Caption { .. } => {
1037                let columns = ColumnSet::first(R::Column::all().len());
1038                Some(
1039                    context
1040                        .rows()
1041                        .map(|row| (row.key().clone(), columns))
1042                        .collect(),
1043                )
1044            }
1045            SelectableLine::Row { .. } => None,
1046        }
1047    }
1048}
1049
1050/// Branches as captions: a tree row with shown children (a branch) takes
1051/// the focus but not the selection, as a group caption does, and a command
1052/// over one acts on the leaves of its subtree that pass the view, those
1053/// under collapsed branches too, in the order of `Projection::rows`, with
1054/// every column. Cmd/Ctrl+A, Shift ranges and drags leave branches out.
1055/// Group captions and leaves behave as with [`FocusCaptions`].
1056///
1057/// A leaf that gets children stays selected (by key); new gestures leave
1058/// it out.
1059#[derive(Clone, Copy, Debug, Default)]
1060pub struct FocusBranches;
1061
1062impl<R: GridRow> SelectionPolicy<R> for FocusBranches {
1063    fn is_selectable(&self, line: &SelectableLine<'_, R>) -> bool {
1064        match line {
1065            SelectableLine::Row { branch, .. } => branch.is_none(),
1066            SelectableLine::Caption { .. } => false,
1067        }
1068    }
1069
1070    fn line_scope(&self, context: &LineScopeContext<'_, R>) -> Option<ScopedSelection<R::Key>> {
1071        match context.line() {
1072            SelectableLine::Row {
1073                branch: Some(_), ..
1074            } => {
1075                let columns = ColumnSet::first(R::Column::all().len());
1076                Some(
1077                    context
1078                        .lines()
1079                        .filter_map(|line| match line {
1080                            SelectableLine::Row { row, branch: None } => {
1081                                Some((row.key().clone(), columns))
1082                            }
1083                            _ => None,
1084                        })
1085                        .collect(),
1086                )
1087            }
1088            _ => FocusCaptions.line_scope(context),
1089        }
1090    }
1091}
1092
1093/// Where a selection gesture comes from.
1094#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1095#[non_exhaustive]
1096pub enum GestureInput {
1097    /// A navigation Action.
1098    Keyboard,
1099    /// A primary-button press.
1100    Pointer,
1101    /// A press that opens the context menu ([`ContextMenuGesture`]), before
1102    /// the menu.
1103    ContextPointer,
1104}
1105
1106/// How a gesture changes the selection; the target always gets the focus.
1107#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1108#[non_exhaustive]
1109pub enum SelectionEdit {
1110    /// Select only the target; it becomes the anchor.
1111    Replace,
1112    /// Select the range from the anchor to the target, dropping the rest.
1113    Extend,
1114    /// Add the range from the anchor to the target to the selection.
1115    AddRange,
1116    /// Flip the target; it becomes the anchor.
1117    Toggle,
1118    /// Leave the selection and the anchor as they are.
1119    Keep,
1120}
1121
1122/// What a [`SelectionGesture`] knows about a press or a navigation command.
1123#[derive(Clone, Copy, Debug)]
1124pub struct SelectionGestureContext {
1125    input: GestureInput,
1126    modifiers: Modifiers,
1127    extending: bool,
1128    target_selected: bool,
1129    mode: SelectionMode,
1130}
1131
1132impl SelectionGestureContext {
1133    pub(crate) fn new(
1134        input: GestureInput,
1135        modifiers: Modifiers,
1136        extending: bool,
1137        target_selected: bool,
1138        mode: SelectionMode,
1139    ) -> Self {
1140        Self {
1141            input,
1142            modifiers,
1143            extending,
1144            target_selected,
1145            mode,
1146        }
1147    }
1148
1149    /// Where the gesture comes from.
1150    pub fn input(&self) -> GestureInput {
1151        self.input
1152    }
1153
1154    /// The pointer's modifiers; none for keyboard input.
1155    pub fn modifiers(&self) -> Modifiers {
1156        self.modifiers
1157    }
1158
1159    /// Returns true for a keyboard `ExtendSelection…` command.
1160    pub fn is_extending(&self) -> bool {
1161        self.extending
1162    }
1163
1164    /// Returns true if the target row (in the cell modes, cell) is selected
1165    /// now.
1166    pub fn is_target_selected(&self) -> bool {
1167        self.target_selected
1168    }
1169
1170    /// The selection mode.
1171    pub fn mode(&self) -> SelectionMode {
1172        self.mode
1173    }
1174}
1175
1176/// How a press or a navigation command edits the selection. Called once
1177/// per gesture on the UI thread. Space toggles and Cmd/Ctrl+A selects all
1178/// whatever it says.
1179///
1180/// The default is [`StandardSelectionGesture`], the Windows and macOS
1181/// conventions with Excel's "navigation selects". Others are plausible: as
1182/// AG Grid (navigation only moves the focus, `Keep`, and Space selects), as
1183/// Finder (Shift adds a range), or touch (a press toggles).
1184pub trait SelectionGesture: 'static {
1185    /// The edit the gesture makes.
1186    fn edit(&self, context: &SelectionGestureContext) -> SelectionEdit;
1187}
1188
1189/// Windows and macOS conventions; navigation selects, as in Excel.
1190///
1191/// A press replaces the selection; Shift extends it from the anchor;
1192/// Cmd on macOS, Ctrl elsewhere, toggles; both add a range. A context press
1193/// selects an unselected row and keeps a selected one. A navigation command
1194/// replaces; its `ExtendSelection…` variant extends.
1195#[derive(Clone, Copy, Debug, Default)]
1196pub struct StandardSelectionGesture;
1197
1198impl SelectionGesture for StandardSelectionGesture {
1199    fn edit(&self, context: &SelectionGestureContext) -> SelectionEdit {
1200        let modifiers = context.modifiers();
1201        match context.input() {
1202            GestureInput::Pointer => match (modifiers.shift, modifiers.secondary()) {
1203                (true, true) => SelectionEdit::AddRange,
1204                (true, false) => SelectionEdit::Extend,
1205                (false, true) => SelectionEdit::Toggle,
1206                (false, false) => SelectionEdit::Replace,
1207            },
1208            GestureInput::ContextPointer if context.is_target_selected() => SelectionEdit::Keep,
1209            GestureInput::ContextPointer => SelectionEdit::Replace,
1210            GestureInput::Keyboard if context.is_extending() => SelectionEdit::Extend,
1211            GestureInput::Keyboard => SelectionEdit::Replace,
1212        }
1213    }
1214}
1215
1216/// The part of the grid a press lands on.
1217#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1218#[non_exhaustive]
1219pub enum GridArea {
1220    /// The rows and the empty area below them.
1221    Body,
1222    /// The column header: its cells, bands and resize handles, and the
1223    /// header right of the columns.
1224    Header,
1225}
1226
1227/// What a [`ContextMenuGesture`] knows about a press.
1228#[derive(Clone, Copy, Debug)]
1229pub struct ContextMenuGestureContext {
1230    button: MouseButton,
1231    modifiers: Modifiers,
1232    click_count: usize,
1233    area: GridArea,
1234}
1235
1236impl ContextMenuGestureContext {
1237    pub(crate) fn new(
1238        button: MouseButton,
1239        modifiers: Modifiers,
1240        click_count: usize,
1241        area: GridArea,
1242    ) -> Self {
1243        Self {
1244            button,
1245            modifiers,
1246            click_count,
1247            area,
1248        }
1249    }
1250
1251    /// The pressed button.
1252    pub fn button(&self) -> MouseButton {
1253        self.button
1254    }
1255
1256    /// The modifier keys held during the press.
1257    pub fn modifiers(&self) -> Modifiers {
1258        self.modifiers
1259    }
1260
1261    /// The press's place in a click series: 1 for a single press, 2 for the
1262    /// second press of a double click.
1263    pub fn click_count(&self) -> usize {
1264        self.click_count
1265    }
1266
1267    /// Where the press lands.
1268    pub fn area(&self) -> GridArea {
1269        self.area
1270    }
1271}
1272
1273/// Which press opens the context menu. Called once per press on the UI
1274/// thread, on the body and on the header; the `ShowContextMenu` Action
1275/// opens the menu whatever it says.
1276///
1277/// On the body, a press that opens the menu first edits the selection as
1278/// [`GestureInput::ContextPointer`] through the [`SelectionGesture`]; any
1279/// other primary-button press is a [`GestureInput::Pointer`] press. On the
1280/// header it opens the column's menu instead of sorting or resizing.
1281///
1282/// The default is [`StandardContextMenuGesture`]. Others are plausible: the
1283/// right button only, so a Ctrl+click on macOS is a plain press; Ctrl+click
1284/// on every platform; no pointer menu at all, only the keyboard's.
1285pub trait ContextMenuGesture: 'static {
1286    /// Returns true if the press opens the context menu.
1287    fn opens_menu(&self, context: &ContextMenuGestureContext) -> bool;
1288}
1289
1290/// The platform conventions: a right click, and on macOS a Ctrl+click
1291/// without Cmd, open the menu, on the body and on the header alike. On
1292/// macOS Cmd+click toggles the selection, so Ctrl+click is free.
1293#[derive(Clone, Copy, Debug, Default)]
1294pub struct StandardContextMenuGesture;
1295
1296impl ContextMenuGesture for StandardContextMenuGesture {
1297    fn opens_menu(&self, context: &ContextMenuGestureContext) -> bool {
1298        let (button, modifiers) = (context.button(), context.modifiers());
1299        button == MouseButton::Right
1300            || (cfg!(target_os = "macos")
1301                && button == MouseButton::Left
1302                && modifiers.control
1303                && !modifiers.platform)
1304    }
1305}
1306
1307/// Where a press lands on a line that expands: a group caption or a tree
1308/// row with children.
1309#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1310#[non_exhaustive]
1311pub enum ExpandArea {
1312    /// The disclosure chevron of a caption or a tree row.
1313    Chevron,
1314    /// A caption outside its chevron: its text or an aggregate cell.
1315    Caption,
1316    /// The tree column's cell of a tree row, outside its chevron.
1317    TreeCell,
1318}
1319
1320/// How a press expands or collapses its line.
1321#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1322#[non_exhaustive]
1323pub enum ExpandEdit {
1324    /// The line takes the other state.
1325    Toggle,
1326    /// The line takes the other state, and every line below it the same.
1327    ToggleDeep,
1328}
1329
1330/// What an [`ExpandGesture`] knows about a press.
1331#[derive(Clone, Copy, Debug)]
1332pub struct ExpandGestureContext {
1333    area: ExpandArea,
1334    button: MouseButton,
1335    modifiers: Modifiers,
1336    click_count: usize,
1337}
1338
1339impl ExpandGestureContext {
1340    pub(crate) fn new(
1341        area: ExpandArea,
1342        button: MouseButton,
1343        modifiers: Modifiers,
1344        click_count: usize,
1345    ) -> Self {
1346        Self {
1347            area,
1348            button,
1349            modifiers,
1350            click_count,
1351        }
1352    }
1353
1354    /// Where the press lands.
1355    pub fn area(&self) -> ExpandArea {
1356        self.area
1357    }
1358
1359    /// The pressed button.
1360    pub fn button(&self) -> MouseButton {
1361        self.button
1362    }
1363
1364    /// The modifier keys held during the press.
1365    pub fn modifiers(&self) -> Modifiers {
1366        self.modifiers
1367    }
1368
1369    /// The press's place in a click series: 1 for a single press, 2 for the
1370    /// second press of a double click.
1371    pub fn click_count(&self) -> usize {
1372        self.click_count
1373    }
1374}
1375
1376/// Which press expands or collapses a group caption or a tree row. Called
1377/// once per press on such a line, on the UI thread, after the
1378/// [`ContextMenuGesture`]. A press on a caption still focuses it; a press
1379/// on a tree row's chevron leaves the focus and the selection as they are,
1380/// as Finder's disclosure triangle does; a press on a chevron is never a
1381/// click. The keys (`ExpandRow`, `CollapseRow`, ←/→ on a caption) expand
1382/// whatever it says.
1383///
1384/// The default is [`StandardExpandGesture`]. Others are plausible: a single
1385/// press on a caption toggles (Syncfusion); a double click on a caption
1386/// confirms instead; no double click at all.
1387pub trait ExpandGesture: 'static {
1388    /// The edit the press makes, if any.
1389    fn edit(&self, context: &ExpandGestureContext) -> Option<ExpandEdit>;
1390}
1391
1392/// A press on a chevron toggles, with Alt (Option on macOS) the line and
1393/// everything below it, as Finder's disclosure triangle; only the first
1394/// press of a series counts. The second press of a double click on a
1395/// caption toggles it (DevExpress, Windows Explorer). A press on a tree
1396/// row's cell does nothing here: a double click confirms, as on any row.
1397#[derive(Clone, Copy, Debug, Default)]
1398pub struct StandardExpandGesture;
1399
1400impl ExpandGesture for StandardExpandGesture {
1401    fn edit(&self, context: &ExpandGestureContext) -> Option<ExpandEdit> {
1402        if context.button() != MouseButton::Left {
1403            return None;
1404        }
1405        match (context.area(), context.click_count()) {
1406            (ExpandArea::Chevron, 1) if context.modifiers().alt => Some(ExpandEdit::ToggleDeep),
1407            (ExpandArea::Chevron, 1) | (ExpandArea::Caption, 2) => Some(ExpandEdit::Toggle),
1408            _ => None,
1409        }
1410    }
1411}
1412
1413// The edit seams: which input opens a cell editor, and how an edit behaves
1414// under live data.
1415
1416/// What an [`EditGesture`] knows about an input on a cell that could edit
1417/// it: a cell of a column with an editor that the delegate's
1418/// `is_editable` lets edit.
1419#[derive(Clone, Debug)]
1420pub struct EditGestureContext {
1421    input: EditInput,
1422    modifiers: Modifiers,
1423    content: CellContent,
1424}
1425
1426impl EditGestureContext {
1427    pub(crate) fn new(input: EditInput, modifiers: Modifiers, content: CellContent) -> Self {
1428        Self {
1429            input,
1430            modifiers,
1431            content,
1432        }
1433    }
1434
1435    /// The input: typing, a press, a command.
1436    pub fn input(&self) -> &EditInput {
1437        &self.input
1438    }
1439
1440    /// The modifiers held.
1441    pub fn modifiers(&self) -> Modifiers {
1442        self.modifiers
1443    }
1444
1445    /// What the cell's column paints: a press on a `Control` cell is the
1446    /// element's own.
1447    pub fn content(&self) -> CellContent {
1448        self.content
1449    }
1450}
1451
1452/// The Action that committed an editor: which key, as the editor's key
1453/// bindings name it (`CommitEdit`, `CommitEditUp`, `CommitEditDown`,
1454/// `CommitEditNext`, `CommitEditPrev`).
1455#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1456#[non_exhaustive]
1457pub enum EditCommitKey {
1458    /// Enter (`CommitEdit`).
1459    Enter,
1460    /// Shift+Enter or ↑ (`CommitEditUp`).
1461    Up,
1462    /// ↓ (`CommitEditDown`).
1463    Down,
1464    /// Tab (`CommitEditNext`).
1465    Next,
1466    /// Shift+Tab (`CommitEditPrev`).
1467    Prev,
1468}
1469
1470/// Where the focus goes after a commit.
1471#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1472#[non_exhaustive]
1473pub enum AfterCommit {
1474    /// The committed cell keeps the focus.
1475    Stay,
1476    /// The focus moves.
1477    Move(CellMove),
1478    /// The focus moves and the editor opens there, if that cell takes an
1479    /// edit.
1480    MoveAndEdit(CellMove),
1481}
1482
1483/// A move of the focus after a commit.
1484#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1485#[non_exhaustive]
1486pub enum CellMove {
1487    /// To the row above.
1488    Up,
1489    /// To the row below.
1490    Down,
1491    /// To the visible column on the left.
1492    Left,
1493    /// To the visible column on the right.
1494    Right,
1495    /// To the next cell that takes an edit, left to right, then down.
1496    NextEditable,
1497    /// To the previous cell that takes an edit, right to left, then up.
1498    PrevEditable,
1499}
1500
1501/// Which input opens the editor of an editable cell, and where the focus
1502/// goes after a commit. Asked once per input on the UI thread.
1503///
1504/// The default is [`StandardEditGesture`], Excel's. An AG Grid-like gesture
1505/// opens the editor on a single click, stays on Enter and edits the next
1506/// editable cell on Tab (`MoveAndEdit(CellMove::NextEditable)`).
1507pub trait EditGesture: 'static {
1508    /// Returns true if `context.input()` opens the editor. `Command` (F2)
1509    /// and `Clear` (Backspace, Delete) are Actions: rebind their keys
1510    /// instead of refusing them here.
1511    fn starts_edit(&self, context: &EditGestureContext) -> bool;
1512
1513    /// Where the focus goes after a commit by `key`.
1514    fn after_commit(&self, key: EditCommitKey) -> AfterCommit;
1515}
1516
1517/// Excel's: typing and a double click edit (not on a `Control` cell,
1518/// whose element takes its own presses), a single click does not; Enter
1519/// commits and moves down, Shift+Enter and ↑ up, ↓ down, Tab right,
1520/// Shift+Tab left.
1521#[derive(Clone, Copy, Debug, Default)]
1522pub struct StandardEditGesture;
1523
1524impl EditGesture for StandardEditGesture {
1525    fn starts_edit(&self, context: &EditGestureContext) -> bool {
1526        match context.input() {
1527            EditInput::Press { click_count } => {
1528                *click_count >= 2 && context.content() != CellContent::Control
1529            }
1530            _ => true,
1531        }
1532    }
1533
1534    fn after_commit(&self, key: EditCommitKey) -> AfterCommit {
1535        AfterCommit::Move(match key {
1536            EditCommitKey::Enter | EditCommitKey::Down => CellMove::Down,
1537            EditCommitKey::Up => CellMove::Up,
1538            EditCommitKey::Next => CellMove::Right,
1539            EditCommitKey::Prev => CellMove::Left,
1540        })
1541    }
1542}
1543
1544/// What an [`EditPolicy`] knows about the open edit.
1545#[derive(Clone, Copy, Debug)]
1546pub struct EditPolicyContext<'a> {
1547    base: &'a OwnedValue,
1548    current: &'a OwnedValue,
1549    draft: Option<&'a OwnedValue>,
1550    dirty: bool,
1551}
1552
1553impl<'a> EditPolicyContext<'a> {
1554    pub(crate) fn new(
1555        base: &'a OwnedValue,
1556        current: &'a OwnedValue,
1557        draft: Option<&'a OwnedValue>,
1558        dirty: bool,
1559    ) -> Self {
1560        Self {
1561            base,
1562            current,
1563            draft,
1564            dirty,
1565        }
1566    }
1567
1568    /// The cell's value when the edit started.
1569    pub fn base(&self) -> &'a OwnedValue {
1570        self.base
1571    }
1572
1573    /// The cell's value in the store now.
1574    pub fn current(&self) -> &'a OwnedValue {
1575        self.current
1576    }
1577
1578    /// The draft as a value of the column; `None` if it does not parse.
1579    pub fn draft(&self) -> Option<&'a OwnedValue> {
1580        self.draft
1581    }
1582
1583    /// Returns true if the user changed the draft since the editor opened.
1584    pub fn is_dirty(&self) -> bool {
1585        self.dirty
1586    }
1587}
1588
1589/// How an edit that leaves the editor ends.
1590#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1591#[non_exhaustive]
1592pub enum EditEnd {
1593    /// Commit the draft.
1594    Commit,
1595    /// Close the editor without a commit.
1596    Cancel,
1597}
1598
1599/// What happens to the draft when the edited cell's value changes in the
1600/// store.
1601#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1602#[non_exhaustive]
1603pub enum DraftAction {
1604    /// Keep the draft; the editor shows the conflict.
1605    Keep,
1606    /// Replace the draft with the new value.
1607    Replace,
1608    /// Close the editor without a commit.
1609    Cancel,
1610}
1611
1612/// How an edit session behaves around its commit and under live data.
1613/// Asked once per event on the UI thread.
1614///
1615/// The default is [`StandardEditPolicy`].
1616pub trait EditPolicy: 'static {
1617    /// The focus left an open editor, or a press landed outside it.
1618    fn on_blur(&self, context: &EditPolicyContext<'_>) -> EditEnd;
1619
1620    /// Returns true if a commit equal to the value the edit started from
1621    /// still emits `EditCommit`. Default: no, the editor closes with
1622    /// `EditCancel { reason: Unchanged }`.
1623    fn commit_unchanged(&self) -> bool {
1624        false
1625    }
1626
1627    /// The edited cell's value changed in the store.
1628    fn on_value_change(&self, context: &EditPolicyContext<'_>) -> DraftAction;
1629
1630    /// The edited row left the shown lines without a user action: a
1631    /// filter, a collapsed group.
1632    fn on_hidden(&self, context: &EditPolicyContext<'_>) -> EditEnd;
1633
1634    /// How long an accepted value is shown while the store does not have it
1635    /// yet. Default: 5 s.
1636    fn settle_timeout(&self) -> Duration {
1637        Duration::from_secs(5)
1638    }
1639
1640    /// How long the error mark of a failed commit stays; `None` (the
1641    /// default): until the cell is edited again or
1642    /// `LiveGridState::clear_edit_error`.
1643    fn error_mark_timeout(&self) -> Option<Duration> {
1644        None
1645    }
1646}
1647
1648/// As [`StandardEditPolicy`], but a draft the user has not touched follows
1649/// the store: a new value replaces it (`DraftAction::Replace`); a touched
1650/// draft is kept and shows the conflict.
1651#[derive(Clone, Copy, Debug, Default)]
1652pub struct FollowLiveValue;
1653
1654impl EditPolicy for FollowLiveValue {
1655    fn on_blur(&self, context: &EditPolicyContext<'_>) -> EditEnd {
1656        StandardEditPolicy.on_blur(context)
1657    }
1658
1659    fn on_value_change(&self, context: &EditPolicyContext<'_>) -> DraftAction {
1660        if context.is_dirty() {
1661            DraftAction::Keep
1662        } else {
1663            DraftAction::Replace
1664        }
1665    }
1666
1667    fn on_hidden(&self, context: &EditPolicyContext<'_>) -> EditEnd {
1668        StandardEditPolicy.on_hidden(context)
1669    }
1670}
1671
1672/// The default edit policy: leaving the editor commits a valid draft and
1673/// cancels an invalid one; a draft is kept when the store changes the
1674/// cell, and the conflict shows; an edited row that leaves the shown lines
1675/// cancels the edit; an accepted value waits 5 s for the store; an error
1676/// mark stays until the next edit.
1677#[derive(Clone, Copy, Debug, Default)]
1678pub struct StandardEditPolicy;
1679
1680impl EditPolicy for StandardEditPolicy {
1681    fn on_blur(&self, context: &EditPolicyContext<'_>) -> EditEnd {
1682        if context.draft().is_some() {
1683            EditEnd::Commit
1684        } else {
1685            EditEnd::Cancel
1686        }
1687    }
1688
1689    fn on_value_change(&self, _: &EditPolicyContext<'_>) -> DraftAction {
1690        DraftAction::Keep
1691    }
1692
1693    fn on_hidden(&self, _: &EditPolicyContext<'_>) -> EditEnd {
1694        EditEnd::Cancel
1695    }
1696}
1697
1698// Which presses drag rows.
1699
1700/// A primary press on a data row, as a [`RowDragGesture`] sees it.
1701pub struct RowDragPressContext<'a, R: GridRow> {
1702    line: SelectableLine<'a, R>,
1703    column: R::Column,
1704    was_selected: bool,
1705    modifiers: Modifiers,
1706    mode: SelectionMode,
1707    click_count: usize,
1708}
1709
1710impl<'a, R: GridRow> RowDragPressContext<'a, R> {
1711    pub(crate) fn new(
1712        line: SelectableLine<'a, R>,
1713        column: R::Column,
1714        was_selected: bool,
1715        modifiers: Modifiers,
1716        mode: SelectionMode,
1717        click_count: usize,
1718    ) -> Self {
1719        Self {
1720            line,
1721            column,
1722            was_selected,
1723            modifiers,
1724            mode,
1725            click_count,
1726        }
1727    }
1728
1729    /// The pressed line: a data row, a tree row too. Group captions,
1730    /// chevrons, pinned rows and the empty area below the rows never drag.
1731    pub fn line(&self) -> &SelectableLine<'a, R> {
1732        &self.line
1733    }
1734
1735    /// The pressed cell's column: a drag handle's, for a gesture that
1736    /// drags from one column only.
1737    pub fn column(&self) -> R::Column {
1738        self.column
1739    }
1740
1741    /// Returns true if the pressed row was selected before the press.
1742    pub fn was_selected(&self) -> bool {
1743        self.was_selected
1744    }
1745
1746    /// The modifiers held.
1747    pub fn modifiers(&self) -> Modifiers {
1748        self.modifiers
1749    }
1750
1751    /// The grid's selection mode.
1752    pub fn mode(&self) -> SelectionMode {
1753        self.mode
1754    }
1755
1756    /// The press's place in a series: 2 for the second press of a double
1757    /// click.
1758    pub fn click_count(&self) -> usize {
1759        self.click_count
1760    }
1761}
1762
1763/// Which presses on the rows may become a row drag. A press that may starts
1764/// no drag-selection; past [`Self::threshold`] the drag starts, carrying the
1765/// command scope of the pressed row. Asked once per press.
1766///
1767/// The default is [`NoRowDrag`]: rows do not drag. [`SelectedRowsDrag`] is
1768/// the built-in alternative, LiveGrid's two-phase gesture.
1769pub trait RowDragGesture<R: GridRow>: 'static {
1770    /// Returns true if `press` may become a row drag.
1771    fn is_drag_press(&self, press: &RowDragPressContext<'_, R>) -> bool;
1772
1773    /// How far the pointer travels from the press before the drag starts.
1774    /// Default: 10 px.
1775    fn threshold(&self) -> Pixels {
1776        px(10.)
1777    }
1778
1779    /// Returns false if no press ever drags: the grid then installs no drag
1780    /// listeners at all. Default: true.
1781    fn is_enabled(&self) -> bool {
1782        true
1783    }
1784}
1785
1786/// No press drags rows. The default: without an application handling
1787/// `Drop`, a drag would only get in the way of the selection.
1788#[derive(Clone, Copy, Debug, Default)]
1789pub struct NoRowDrag;
1790
1791impl<R: GridRow> RowDragGesture<R> for NoRowDrag {
1792    fn is_drag_press(&self, _: &RowDragPressContext<'_, R>) -> bool {
1793        false
1794    }
1795
1796    fn is_enabled(&self) -> bool {
1797        false
1798    }
1799}
1800
1801/// LiveGrid's two-phase gesture: the first press of a series, without Shift
1802/// and without the platform toggle (Cmd on macOS, Ctrl elsewhere), on a data
1803/// row selected before the press drags the selection once the pointer is
1804/// 10 px away. A press on an unselected row selects it and drag-selects as
1805/// before.
1806#[derive(Clone, Copy, Debug)]
1807pub struct SelectedRowsDrag {
1808    threshold: Pixels,
1809}
1810
1811impl SelectedRowsDrag {
1812    /// The gesture with a 10 px threshold.
1813    pub fn new() -> Self {
1814        Self { threshold: px(10.) }
1815    }
1816
1817    /// Sets how far the pointer travels before the drag starts, such as the
1818    /// system's 3 or 4 px.
1819    pub fn with_threshold(mut self, threshold: Pixels) -> Self {
1820        self.threshold = threshold;
1821        self
1822    }
1823}
1824
1825impl Default for SelectedRowsDrag {
1826    fn default() -> Self {
1827        Self::new()
1828    }
1829}
1830
1831impl<R: GridRow> RowDragGesture<R> for SelectedRowsDrag {
1832    fn is_drag_press(&self, press: &RowDragPressContext<'_, R>) -> bool {
1833        let modifiers = press.modifiers();
1834        press.was_selected()
1835            && press.click_count() == 1
1836            && !modifiers.shift
1837            && !modifiers.secondary()
1838            && matches!(press.line(), SelectableLine::Row { .. })
1839    }
1840
1841    fn threshold(&self) -> Pixels {
1842        self.threshold
1843    }
1844}
1845
1846// Where drops land.
1847
1848/// The line under a drag over the rows.
1849#[non_exhaustive]
1850pub enum DropLine<'a, R: GridRow> {
1851    /// A data row; `branch` is set for a tree row with shown children.
1852    Row {
1853        /// The row's key.
1854        key: &'a R::Key,
1855        /// The row's children, in a tree.
1856        branch: Option<Branch>,
1857    },
1858    /// A group caption.
1859    Caption(GroupInfo<'a>),
1860    /// Below the last row, a row fading out, or an empty grid.
1861    Empty,
1862}
1863
1864/// A drag over the rows, as a [`RowDropPolicy`] sees it.
1865pub struct RowDropContext<'a, R: GridRow> {
1866    line: DropLine<'a, R>,
1867    offset: f32,
1868    rows: Option<&'a RowDrag<R::Key>>,
1869    value: Option<&'a dyn std::any::Any>,
1870    own: bool,
1871    modifiers: Modifiers,
1872    sorted: bool,
1873    grouped: bool,
1874    tree: bool,
1875    last_row: Option<&'a R::Key>,
1876    snapshot: &'a crate::store::StoreSnapshot<R>,
1877}
1878
1879impl<'a, R: GridRow> RowDropContext<'a, R> {
1880    #[allow(clippy::too_many_arguments)]
1881    pub(crate) fn new(
1882        line: DropLine<'a, R>,
1883        offset: f32,
1884        rows: Option<&'a RowDrag<R::Key>>,
1885        value: Option<&'a dyn std::any::Any>,
1886        own: bool,
1887        modifiers: Modifiers,
1888        (sorted, grouped, tree): (bool, bool, bool),
1889        last_row: Option<&'a R::Key>,
1890        snapshot: &'a crate::store::StoreSnapshot<R>,
1891    ) -> Self {
1892        Self {
1893            line,
1894            offset,
1895            rows,
1896            value,
1897            own,
1898            modifiers,
1899            sorted,
1900            grouped,
1901            tree,
1902            last_row,
1903            snapshot,
1904        }
1905    }
1906
1907    /// The line under the pointer.
1908    pub fn line(&self) -> &DropLine<'a, R> {
1909        &self.line
1910    }
1911
1912    /// Where the pointer is within the line's height: 0.0 at its top, 1.0
1913    /// at its bottom.
1914    pub fn offset(&self) -> f32 {
1915        self.offset
1916    }
1917
1918    /// The dragged rows of a grid with this key type; `None` for a value
1919    /// of a type registered with `with_drop_payload`.
1920    pub fn rows(&self) -> Option<&'a RowDrag<R::Key>> {
1921        self.rows
1922    }
1923
1924    /// The dragged value of a registered type `T`.
1925    pub fn payload<T: 'static>(&self) -> Option<&'a T> {
1926        self.value?.downcast_ref()
1927    }
1928
1929    /// Returns true if the rows come from this grid.
1930    pub fn is_own(&self) -> bool {
1931        self.own
1932    }
1933
1934    /// The modifiers held.
1935    pub fn modifiers(&self) -> Modifiers {
1936        self.modifiers
1937    }
1938
1939    /// Returns true if the view is sorted: a place between rows would not
1940    /// hold.
1941    pub fn is_sorted(&self) -> bool {
1942        self.sorted
1943    }
1944
1945    /// Returns true if the view is grouped.
1946    pub fn is_grouped(&self) -> bool {
1947        self.grouped
1948    }
1949
1950    /// Returns true if the view is a tree.
1951    pub fn is_tree(&self) -> bool {
1952        self.tree
1953    }
1954
1955    /// The last data row shown, for a drop below the rows; `None` in an
1956    /// empty grid.
1957    pub fn last_row(&self) -> Option<&'a R::Key> {
1958        self.last_row
1959    }
1960
1961    /// A row of the snapshot shown.
1962    pub fn row(&self, key: &R::Key) -> Option<&'a Arc<R>> {
1963        self.snapshot.row(self.snapshot.row_id(key)?)
1964    }
1965}
1966
1967/// Where a drag over the rows would land, or whether the grid refuses it.
1968/// Asked on every move of a drag over the rows and once more at the drop;
1969/// pinned bands, group footers and totals refuse without asking.
1970///
1971/// The default is [`NoRowDrop`]: the grid takes no drop. [`DropOnRows`]
1972/// and [`DropBetweenRows`] are built in.
1973pub trait RowDropPolicy<R: GridRow>: 'static {
1974    /// Where a drop at `drop` lands; `None` refuses it.
1975    fn place(&self, drop: &RowDropContext<'_, R>) -> Option<DropTarget<R::Key>>;
1976
1977    /// Returns false if the grid takes no drop at all: it then installs no
1978    /// drop listener. Default: true.
1979    fn is_enabled(&self) -> bool {
1980        true
1981    }
1982
1983    /// Returns true if a drop with `modifiers` held copies rather than
1984    /// moves: the grid then shows the copy cursor over a target. What a
1985    /// drop does stays the application's, which reads the modifiers of
1986    /// `LiveGridEvent::Drop`. Default: Option (Alt) or Ctrl, on every
1987    /// platform.
1988    fn is_copy(&self, modifiers: Modifiers) -> bool {
1989        modifiers.alt || modifiers.control
1990    }
1991}
1992
1993/// The grid takes no drop. The default.
1994#[derive(Clone, Copy, Debug, Default)]
1995pub struct NoRowDrop;
1996
1997impl<R: GridRow> RowDropPolicy<R> for NoRowDrop {
1998    fn place(&self, _: &RowDropContext<'_, R>) -> Option<DropTarget<R::Key>> {
1999        None
2000    }
2001
2002    fn is_enabled(&self) -> bool {
2003        false
2004    }
2005}
2006
2007/// Onto data rows (tree rows too), group captions and the empty area below
2008/// the rows; any source.
2009#[derive(Clone, Copy, Debug, Default)]
2010pub struct DropOnRows;
2011
2012impl<R: GridRow> RowDropPolicy<R> for DropOnRows {
2013    fn place(&self, drop: &RowDropContext<'_, R>) -> Option<DropTarget<R::Key>> {
2014        Some(match drop.line() {
2015            DropLine::Row { key, .. } => DropTarget::Row {
2016                key: (*key).clone(),
2017                place: DropPlace::Onto,
2018            },
2019            DropLine::Caption(group) => DropTarget::Group(group.path().clone()),
2020            DropLine::Empty => DropTarget::Empty,
2021        })
2022    }
2023}
2024
2025/// Before or after a data row by the half the pointer is in; in a tree the
2026/// top third is before, the middle onto, the bottom third after. Onto group
2027/// captions. Below the rows, after the last one; an empty grid takes
2028/// `Empty`. Refuses in a sorted flat view, where a place between rows would
2029/// not hold.
2030#[derive(Clone, Copy, Debug, Default)]
2031pub struct DropBetweenRows;
2032
2033impl<R: GridRow> RowDropPolicy<R> for DropBetweenRows {
2034    fn place(&self, drop: &RowDropContext<'_, R>) -> Option<DropTarget<R::Key>> {
2035        if drop.is_sorted() && !drop.is_grouped() && !drop.is_tree() {
2036            return None;
2037        }
2038        let row = |key: &R::Key, place| DropTarget::Row {
2039            key: key.clone(),
2040            place,
2041        };
2042        Some(match drop.line() {
2043            DropLine::Row { key, .. } if drop.is_tree() => {
2044                let place = match drop.offset() {
2045                    offset if offset < 1. / 3. => DropPlace::Before,
2046                    offset if offset < 2. / 3. => DropPlace::Onto,
2047                    _ => DropPlace::After,
2048                };
2049                row(key, place)
2050            }
2051            DropLine::Row { key, .. } if drop.offset() < 0.5 => row(key, DropPlace::Before),
2052            DropLine::Row { key, .. } => row(key, DropPlace::After),
2053            DropLine::Caption(group) => DropTarget::Group(group.path().clone()),
2054            DropLine::Empty => match drop.last_row() {
2055                Some(key) => row(key, DropPlace::After),
2056                None => DropTarget::Empty,
2057            },
2058        })
2059    }
2060}
2061
2062#[cfg(test)]
2063mod tests {
2064    use gpui_kit::px;
2065
2066    use super::*;
2067
2068    fn ix(i: u16) -> ColumnIx {
2069        ColumnIx::new(i)
2070    }
2071
2072    /// c0, band A [c1, band B [c2, c3], c4], c5, all visible.
2073    fn banded() -> (Vec<ColumnIx>, Vec<Box<[&'static str]>>) {
2074        let columns = (0..6).map(ix).collect();
2075        let bands: Vec<Box<[&'static str]>> = vec![
2076            Box::new([]),
2077            Box::new(["a"]),
2078            Box::new(["a", "b"]),
2079            Box::new(["a", "b"]),
2080            Box::new(["a"]),
2081            Box::new([]),
2082        ];
2083        (columns, bands)
2084    }
2085
2086    #[test]
2087    fn test_banded_move_policy() {
2088        let (columns, bands) = banded();
2089        let visible = ColumnSet::first(6);
2090        let context = ColumnMoveContext::new(ColumnZone::Scrolling, &columns, visible, &bands);
2091        let allowed = |block: Range<usize>, gap: usize| BandedMove.is_allowed(&context, block, gap);
2092
2093        // c2 moves only among the children of B.
2094        assert!(allowed(3..4, 2));
2095        assert!(allowed(2..3, 4));
2096        assert!(!allowed(2..3, 1), "out of B to the left");
2097        assert!(!allowed(2..3, 5), "out of B to the right");
2098        // c1 moves among the children of A: around B as a whole, never into it.
2099        assert!(allowed(1..2, 4));
2100        assert!(allowed(1..2, 5));
2101        assert!(!allowed(1..2, 3), "inside the sibling band B");
2102        assert!(!allowed(1..2, 0), "out of A");
2103        // B as a whole is a child of A.
2104        assert!(allowed(2..4, 1));
2105        assert!(allowed(2..4, 5));
2106        assert!(!allowed(2..4, 0));
2107        // A as a whole moves among the zone's top-level items.
2108        assert!(allowed(1..5, 0));
2109        assert!(allowed(1..5, 6));
2110        // c0 cannot land inside A.
2111        assert!(allowed(0..1, 5));
2112        assert!(allowed(0..1, 6));
2113        assert!(!allowed(0..1, 2));
2114        assert!(!allowed(0..1, 4));
2115
2116        // A zone change takes the whole top-level band.
2117        assert_eq!(BandedMove.zone_block(&context, ix(2)), 1..5);
2118        assert_eq!(BandedMove.zone_block(&context, ix(4)), 1..5);
2119        assert_eq!(BandedMove.zone_block(&context, ix(0)), 0..1);
2120        assert_eq!(BandedMove.zone_block(&context, ix(9)), 0..0);
2121
2122        // Free moves: every gap of the zone, one column at a time.
2123        assert!(FreeMove.is_allowed(&context, 0..1, 2));
2124        assert!(FreeMove.is_allowed(&context, 2..3, 6));
2125        assert!(!FreeMove.is_allowed(&context, 2..3, 7));
2126        assert_eq!(FreeMove.zone_block(&context, ix(2)), 2..3);
2127
2128        // `normalize` gathers each band at the place of its first column,
2129        // nested bands too, in the zone of its first column.
2130        let mut zones = vec![ColumnZone::Scrolling; 6];
2131        zones[4] = ColumnZone::FrozenRight;
2132        let mut order = vec![ix(2), ix(0), ix(1), ix(3), ix(5), ix(4)];
2133        BandedMove.normalize(&mut ColumnOrder::new(&mut order, &mut zones, &bands));
2134        assert_eq!(order, [2, 3, 1, 4, 0, 5].map(ix));
2135        assert_eq!(zones[4], ColumnZone::Scrolling);
2136        // A valid order stays as it is.
2137        let mut order = vec![ix(5), ix(1), ix(3), ix(2), ix(4), ix(0)];
2138        let before = order.clone();
2139        BandedMove.normalize(&mut ColumnOrder::new(&mut order, &mut zones, &bands));
2140        assert_eq!(order, before);
2141        // Free moves keep any order.
2142        let mut order = vec![ix(2), ix(0), ix(1), ix(3), ix(5), ix(4)];
2143        FreeMove.normalize(&mut ColumnOrder::new(&mut order, &mut zones, &bands));
2144        assert_eq!(order, [2, 0, 1, 3, 5, 4].map(ix));
2145
2146        // `reorder` accepts permutations only.
2147        let mut order = (0..6).map(ix).collect::<Vec<_>>();
2148        let mut view = ColumnOrder::new(&mut order, &mut zones, &bands);
2149        assert!(!view.reorder(&[ix(0), ix(0), ix(1), ix(2), ix(3), ix(4)]));
2150        assert!(!view.reorder(&[ix(0)]));
2151        assert!(view.reorder(&[5, 4, 3, 2, 1, 0].map(ix)));
2152        assert_eq!(view.columns(), [5, 4, 3, 2, 1, 0].map(ix));
2153        assert_eq!(view.bands(ix(3)), ["a", "b"]);
2154    }
2155
2156    #[test]
2157    fn test_visible_rows_autofit() {
2158        let mut measured = Vec::new();
2159        let mut measure = |display_ix: usize| {
2160            measured.push(display_ix);
2161            // Row 4 was removed; row 5 is the widest painted one.
2162            (display_ix != 4).then(|| px(display_ix as f32 * 10.))
2163        };
2164        let mut context = AutofitContext::new(px(25.), 3..6, 1_000, &mut measure);
2165        assert_eq!(context.row_count(), 1_000);
2166        assert_eq!(VisibleRowsAutofit.content_width(&mut context), px(50.));
2167        assert_eq!(measured, [3, 4, 5], "rows off screen are not measured");
2168
2169        // The header is wider than every painted row.
2170        let mut measure = |_: usize| Some(px(10.));
2171        let mut context = AutofitContext::new(px(80.), 0..3, 3, &mut measure);
2172        assert_eq!(VisibleRowsAutofit.content_width(&mut context), px(80.));
2173        // An empty projection fits the header alone.
2174        let mut measure = |_: usize| None;
2175        let mut context = AutofitContext::new(px(42.), 0..0, 0, &mut measure);
2176        assert_eq!(context.pinned_width(), px(0.));
2177        assert_eq!(VisibleRowsAutofit.content_width(&mut context), px(42.));
2178
2179        // A pinned row (a total) wider than the header and the data rows.
2180        let mut measure = |_: usize| Some(px(30.));
2181        let mut context =
2182            AutofitContext::new(px(20.), 0..3, 3, &mut measure).with_pinned_width(px(64.));
2183        assert_eq!(context.pinned_width(), px(64.));
2184        assert_eq!(VisibleRowsAutofit.content_width(&mut context), px(64.));
2185
2186        // A total wider than everything else.
2187        let mut measure = |_: usize| Some(px(30.));
2188        let mut context = AutofitContext::new(px(20.), 0..3, 3, &mut measure)
2189            .with_pinned_width(px(64.))
2190            .with_summary_width(px(90.));
2191        assert_eq!(context.summary_width(), px(90.));
2192        assert_eq!(VisibleRowsAutofit.content_width(&mut context), px(90.));
2193    }
2194
2195    #[test]
2196    fn test_standard_sort_cycle() {
2197        use SortDirection::{Ascending, Descending};
2198        let next = |sort: &[(ColumnIx, SortDirection)], column: u16, shift: bool| {
2199            let modifiers = if shift {
2200                Modifiers::shift()
2201            } else {
2202                Modifiers::default()
2203            };
2204            StandardSortCycle.next_sort(&SortCycleContext::new(sort, ix(column), modifiers))
2205        };
2206
2207        // Without Shift: ascending, descending, unsorted.
2208        assert_eq!(next(&[], 1, false), [(ix(1), Ascending)]);
2209        assert_eq!(next(&[(ix(1), Ascending)], 1, false), [(ix(1), Descending)]);
2210        assert_eq!(next(&[(ix(1), Descending)], 1, false), []);
2211        // Another column replaces the sort.
2212        assert_eq!(next(&[(ix(1), Descending)], 2, false), [(ix(2), Ascending)]);
2213        // From a multi-column sort, the column steps from its own direction
2214        // and the others go (as AG Grid).
2215        let multi = [(ix(1), Ascending), (ix(2), Ascending), (ix(3), Descending)];
2216        assert_eq!(next(&multi, 2, false), [(ix(2), Descending)]);
2217        assert_eq!(next(&multi, 3, false), []);
2218
2219        // Shift adds the column last.
2220        assert_eq!(
2221            next(&multi, 4, true),
2222            [
2223                (ix(1), Ascending),
2224                (ix(2), Ascending),
2225                (ix(3), Descending),
2226                (ix(4), Ascending)
2227            ]
2228        );
2229        // Shift on an ascending column flips it in place, keeping its priority.
2230        assert_eq!(
2231            next(&multi, 1, true),
2232            [(ix(1), Descending), (ix(2), Ascending), (ix(3), Descending)]
2233        );
2234        // Shift on a descending column removes it; the rest move up.
2235        assert_eq!(
2236            next(&[(ix(3), Descending), (ix(1), Ascending)], 3, true),
2237            [(ix(1), Ascending)]
2238        );
2239        assert_eq!(next(&[], 1, true), [(ix(1), Ascending)]);
2240
2241        let context = SortCycleContext::new(&multi, ix(3), Modifiers::shift());
2242        assert_eq!(context.sort(), multi);
2243        assert_eq!(context.column(), ix(3));
2244        assert!(context.modifiers().shift);
2245        assert_eq!(context.direction(), Some(Descending));
2246    }
2247
2248    #[test]
2249    fn test_reorder_policies() {
2250        let start = Instant::now();
2251        let at = |ms: u64| start + Duration::from_millis(ms);
2252        let context =
2253            |now: u64, inside: bool, user: bool| ReorderContext::new(at(now), at(0), inside, user);
2254
2255        assert_eq!(
2256            LiveReorder.decide(&context(0, true, false)),
2257            ReorderDecision::Apply
2258        );
2259
2260        let hold = HoldReorder::new(Duration::from_millis(500));
2261        assert_eq!(
2262            hold.decide(&context(100, true, false)),
2263            ReorderDecision::HoldUntil(at(500))
2264        );
2265        assert_eq!(
2266            hold.decide(&context(500, true, false)),
2267            ReorderDecision::Apply
2268        );
2269        assert_eq!(
2270            hold.decide(&context(100, false, false)),
2271            ReorderDecision::Apply
2272        );
2273        assert_eq!(
2274            hold.decide(&context(100, true, true)),
2275            ReorderDecision::Apply
2276        );
2277
2278        assert_eq!(
2279            ManualReorder.decide(&context(100, false, false)),
2280            ReorderDecision::Hold
2281        );
2282        assert_eq!(
2283            ManualReorder.decide(&context(100, true, true)),
2284            ReorderDecision::Apply
2285        );
2286
2287        // An open menu holds the order under every built-in policy, except
2288        // for a user action.
2289        let menu =
2290            |now: u64, inside: bool, user: bool| context(now, inside, user).with_menu_open(true);
2291        let policies: [&dyn ReorderPolicy; 3] = [&LiveReorder, &hold, &ManualReorder];
2292        for policy in policies {
2293            for inside in [false, true] {
2294                assert_eq!(
2295                    policy.decide(&menu(100, inside, false)),
2296                    ReorderDecision::Hold
2297                );
2298                assert_eq!(
2299                    policy.decide(&menu(100, inside, true)),
2300                    ReorderDecision::Apply
2301                );
2302            }
2303        }
2304        // Past `max`, `HoldReorder` still holds while the menu is open, and
2305        // applies once it closes.
2306        assert_eq!(hold.decide(&menu(900, true, false)), ReorderDecision::Hold);
2307        assert_eq!(
2308            hold.decide(&context(900, true, false)),
2309            ReorderDecision::Apply
2310        );
2311        // A shown tooltip changes nothing for the built-in policies.
2312        let tooltip = |now: u64, inside: bool| context(now, inside, false).with_tooltip_shown(true);
2313        assert!(tooltip(0, true).is_tooltip_shown());
2314        assert!(!context(0, true, false).is_tooltip_shown());
2315        assert!(!context(0, true, false).is_menu_open());
2316        assert_eq!(
2317            LiveReorder.decide(&tooltip(100, true)),
2318            ReorderDecision::Apply
2319        );
2320        assert_eq!(
2321            hold.decide(&tooltip(100, true)),
2322            ReorderDecision::HoldUntil(at(500))
2323        );
2324        assert_eq!(hold.decide(&tooltip(100, false)), ReorderDecision::Apply);
2325        assert_eq!(
2326            ManualReorder.decide(&tooltip(100, false)),
2327            ReorderDecision::Hold
2328        );
2329    }
2330
2331    #[test]
2332    fn test_standard_selection_gesture() {
2333        use SelectionEdit::*;
2334        let edit = |input, modifiers: Modifiers, extending, selected| {
2335            StandardSelectionGesture.edit(&SelectionGestureContext::new(
2336                input,
2337                modifiers,
2338                extending,
2339                selected,
2340                SelectionMode::RowSet,
2341            ))
2342        };
2343        let secondary = Modifiers::secondary_key();
2344        let shift_secondary = Modifiers {
2345            shift: true,
2346            ..secondary
2347        };
2348        // The standard gesture, case by case.
2349        let pointer = GestureInput::Pointer;
2350        assert_eq!(edit(pointer, Modifiers::default(), false, false), Replace);
2351        assert_eq!(edit(pointer, Modifiers::default(), false, true), Replace);
2352        assert_eq!(edit(pointer, Modifiers::shift(), false, false), Extend);
2353        assert_eq!(edit(pointer, secondary, false, true), Toggle);
2354        assert_eq!(edit(pointer, shift_secondary, false, false), AddRange);
2355        let context = GestureInput::ContextPointer;
2356        assert_eq!(edit(context, Modifiers::default(), false, false), Replace);
2357        assert_eq!(edit(context, Modifiers::default(), false, true), Keep);
2358        let keyboard = GestureInput::Keyboard;
2359        assert_eq!(edit(keyboard, Modifiers::default(), false, true), Replace);
2360        assert_eq!(edit(keyboard, Modifiers::default(), true, false), Extend);
2361
2362        let context = SelectionGestureContext::new(
2363            keyboard,
2364            Modifiers::shift(),
2365            true,
2366            true,
2367            SelectionMode::CellSet,
2368        );
2369        assert_eq!(context.input(), keyboard);
2370        assert!(context.modifiers().shift);
2371        assert!(context.is_extending());
2372        assert!(context.is_target_selected());
2373        assert_eq!(context.mode(), SelectionMode::CellSet);
2374    }
2375
2376    #[test]
2377    fn test_standard_context_menu_gesture() {
2378        let opens = |button, modifiers, click_count, area| {
2379            StandardContextMenuGesture.opens_menu(&ContextMenuGestureContext::new(
2380                button,
2381                modifiers,
2382                click_count,
2383                area,
2384            ))
2385        };
2386        let control = Modifiers::control();
2387        let control_command = Modifiers {
2388            platform: true,
2389            ..control
2390        };
2391        let macos = cfg!(target_os = "macos");
2392        for area in [GridArea::Body, GridArea::Header] {
2393            // The right button, whatever the modifiers and the series.
2394            for modifiers in [Modifiers::default(), Modifiers::shift(), control] {
2395                assert!(opens(MouseButton::Right, modifiers, 1, area));
2396            }
2397            assert!(opens(MouseButton::Right, Modifiers::default(), 2, area));
2398            // The left button: Ctrl without Cmd on macOS only. Cmd on macOS
2399            // and Ctrl elsewhere toggle the selection.
2400            assert!(!opens(MouseButton::Left, Modifiers::default(), 1, area));
2401            assert!(!opens(MouseButton::Left, Modifiers::shift(), 1, area));
2402            assert!(!opens(MouseButton::Left, Modifiers::command(), 1, area));
2403            assert_eq!(opens(MouseButton::Left, control, 1, area), macos);
2404            assert_eq!(opens(MouseButton::Left, control, 2, area), macos);
2405            assert!(!opens(MouseButton::Left, control_command, 1, area));
2406            // Other buttons never.
2407            assert!(!opens(MouseButton::Middle, Modifiers::default(), 1, area));
2408            assert!(!opens(MouseButton::Middle, control, 1, area));
2409        }
2410
2411        let context =
2412            ContextMenuGestureContext::new(MouseButton::Middle, control, 2, GridArea::Header);
2413        assert_eq!(context.button(), MouseButton::Middle);
2414        assert!(context.modifiers().control);
2415        assert_eq!(context.click_count(), 2);
2416        assert_eq!(context.area(), GridArea::Header);
2417    }
2418
2419    #[test]
2420    fn test_drop_policies() {
2421        use std::time::Instant;
2422
2423        use crate::{
2424            highlight::HighlightPolicy,
2425            model::{CellValue, GridColumns},
2426            store::Ingest,
2427        };
2428
2429        #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2430        struct Only;
2431
2432        impl GridColumns for Only {
2433            fn all() -> &'static [Self] {
2434                &[Only]
2435            }
2436
2437            fn id(self) -> &'static str {
2438                "only"
2439            }
2440
2441            fn ix(self) -> ColumnIx {
2442                ColumnIx::new(0)
2443            }
2444        }
2445
2446        struct Row(u32);
2447
2448        impl GridRow for Row {
2449            type Key = u32;
2450            type Column = Only;
2451
2452            fn key(&self) -> &u32 {
2453                &self.0
2454            }
2455
2456            fn value(&self, _: Only) -> CellValue<'_> {
2457                CellValue::Empty
2458            }
2459        }
2460
2461        // The copy cursor: Option or Ctrl by default; a policy may decide
2462        // otherwise.
2463        let held = |alt, control| Modifiers {
2464            alt,
2465            control,
2466            ..Modifiers::default()
2467        };
2468        assert!(RowDropPolicy::<Row>::is_copy(&NoRowDrop, held(true, false)));
2469        assert!(RowDropPolicy::<Row>::is_copy(&NoRowDrop, held(false, true)));
2470        assert!(!RowDropPolicy::<Row>::is_copy(
2471            &NoRowDrop,
2472            held(false, false)
2473        ));
2474        struct NeverCopy;
2475        impl RowDropPolicy<Row> for NeverCopy {
2476            fn place(&self, _: &RowDropContext<'_, Row>) -> Option<DropTarget<u32>> {
2477                None
2478            }
2479
2480            fn is_copy(&self, _: Modifiers) -> bool {
2481                false
2482            }
2483        }
2484        assert!(!NeverCopy.is_copy(held(true, true)));
2485
2486        let snapshot = Ingest::<Row>::new(HighlightPolicy::default(), Instant::now()).snapshot();
2487        let (key, last) = (3, 9);
2488        // A drag at `offset` over the row of `key` (`None`: below the rows)
2489        // in a view `(sorted, grouped, tree)`.
2490        let place = |policy: &dyn RowDropPolicy<Row>,
2491                     row: Option<f32>,
2492                     view: (bool, bool, bool),
2493                     last_row: Option<&u32>| {
2494            let line = match row {
2495                Some(_) => DropLine::Row {
2496                    key: &key,
2497                    branch: None,
2498                },
2499                None => DropLine::Empty,
2500            };
2501            policy.place(&RowDropContext::new(
2502                line,
2503                row.unwrap_or(0.),
2504                None,
2505                None,
2506                false,
2507                Modifiers::default(),
2508                view,
2509                last_row,
2510                &snapshot,
2511            ))
2512        };
2513        let at = |key, place| Some(DropTarget::Row { key, place });
2514        let flat = (false, false, false);
2515        assert!(!RowDropPolicy::<Row>::is_enabled(&NoRowDrop));
2516        assert_eq!(place(&NoRowDrop, Some(0.5), flat, None), None);
2517        assert_eq!(
2518            place(&DropOnRows, Some(0.1), flat, None),
2519            at(3, DropPlace::Onto)
2520        );
2521        assert_eq!(
2522            place(&DropOnRows, None, flat, None),
2523            Some(DropTarget::Empty)
2524        );
2525        // Between rows by the half, in a tree by the third.
2526        assert_eq!(
2527            place(&DropBetweenRows, Some(0.4), flat, None),
2528            at(3, DropPlace::Before)
2529        );
2530        assert_eq!(
2531            place(&DropBetweenRows, Some(0.6), flat, None),
2532            at(3, DropPlace::After)
2533        );
2534        let tree = (false, false, true);
2535        assert_eq!(
2536            place(&DropBetweenRows, Some(0.2), tree, None),
2537            at(3, DropPlace::Before)
2538        );
2539        assert_eq!(
2540            place(&DropBetweenRows, Some(0.5), tree, None),
2541            at(3, DropPlace::Onto)
2542        );
2543        assert_eq!(
2544            place(&DropBetweenRows, Some(0.8), tree, None),
2545            at(3, DropPlace::After)
2546        );
2547        // Below the rows: after the last one; an empty grid takes `Empty`.
2548        assert_eq!(
2549            place(&DropBetweenRows, None, flat, Some(&last)),
2550            at(9, DropPlace::After)
2551        );
2552        assert_eq!(
2553            place(&DropBetweenRows, None, flat, None),
2554            Some(DropTarget::Empty)
2555        );
2556        // Sorted: refused when flat, kept when grouped.
2557        assert_eq!(
2558            place(&DropBetweenRows, Some(0.6), (true, false, false), None),
2559            None
2560        );
2561        assert_eq!(
2562            place(&DropBetweenRows, Some(0.6), (true, true, false), None),
2563            at(3, DropPlace::After)
2564        );
2565    }
2566
2567    #[test]
2568    fn test_selection_scopes() {
2569        use std::{sync::Arc, time::Instant};
2570
2571        use crate::{
2572            engine::{ProjectionEngine as _, Rebuild, ViewSpec},
2573            highlight::HighlightPolicy,
2574            model::{CellValue, GridColumns},
2575            store::{Ingest, Op},
2576        };
2577
2578        #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2579        struct Price;
2580
2581        impl GridColumns for Price {
2582            fn all() -> &'static [Self] {
2583                &[Price]
2584            }
2585
2586            fn id(self) -> &'static str {
2587                "price"
2588            }
2589
2590            fn ix(self) -> ColumnIx {
2591                ColumnIx::new(0)
2592            }
2593        }
2594
2595        struct Row(u32, i64);
2596
2597        impl GridRow for Row {
2598            type Key = u32;
2599            type Column = Price;
2600
2601            fn key(&self) -> &u32 {
2602                &self.0
2603            }
2604
2605            fn value(&self, _: Price) -> CellValue<'_> {
2606                CellValue::Int(self.1)
2607            }
2608        }
2609
2610        // Ten rows sorted by price descending (key 9 first); odd keys shown.
2611        let mut ingest = Ingest::new(HighlightPolicy::default(), Instant::now());
2612        ingest.apply(
2613            (0..10).map(|id| Op::Upsert(Arc::new(Row(id, id.into())))),
2614            Instant::now(),
2615        );
2616        let view = ViewSpec::new()
2617            .with_sort([(Price, SortDirection::Descending)])
2618            .with_filter(ColumnSet::first(1), |row: &Row| row.0 % 2 == 1);
2619        let projection = Rebuild::default().rebuild(&ingest.snapshot(), &view);
2620        // Removed after the projection was built: a tombstone on screen.
2621        ingest.apply([Op::Remove(5), Op::Remove(4)], Instant::now());
2622        let snapshot = ingest.snapshot();
2623
2624        let row = ColumnSet::first(1);
2625        let resolve = |scope: &dyn SelectionScope<Row>, keys: &[u32]| {
2626            // Key 42 never existed; 4 and 5 are removed; 2 and 6 hidden.
2627            let selection = GridSelection::from_rows(keys.iter().map(|key| (*key, row)));
2628            let scoped = scope.resolve(&SelectionScopeContext::new(
2629                &selection,
2630                &projection,
2631                &snapshot,
2632                SelectionMode::RowSet,
2633            ));
2634            assert!((0..scoped.len()).all(|ix| scoped.columns(ix) == row));
2635            assert_eq!(scoped.columns(scoped.len()), ColumnSet::EMPTY);
2636            scoped.keys().to_vec()
2637        };
2638        let few = [1, 6, 2, 5, 9, 4, 42];
2639        let many: Vec<u32> = (0..10).chain([42]).collect();
2640        // The selection within the screen, in display order, both paths.
2641        assert_eq!(resolve(&ProjectionScope, &few), [9, 1]);
2642        assert_eq!(resolve(&ProjectionScope, &many), [9, 7, 3, 1]);
2643        assert!(resolve(&ProjectionScope, &[]).is_empty());
2644        // Hidden rows after the shown ones, by insertion; removed never.
2645        assert_eq!(resolve(&StoreScope, &few), [9, 1, 2, 6]);
2646        assert_eq!(resolve(&StoreScope, &many), [9, 7, 3, 1, 0, 2, 6, 8]);
2647
2648        let empty = GridSelection::default();
2649        let context =
2650            SelectionScopeContext::new(&empty, &projection, &snapshot, SelectionMode::CellSet);
2651        assert_eq!(context.mode(), SelectionMode::CellSet);
2652        assert_eq!(context.projection().len(), 5);
2653        assert_eq!(context.snapshot().len(), 8);
2654        assert!(context.selection().is_empty());
2655        // A scope is collected from (key, columns) pairs.
2656        let scoped: ScopedSelection<u32> = [(3, row)].into_iter().collect();
2657        assert_eq!((scoped.len(), scoped.is_empty()), (1, false));
2658    }
2659
2660    #[test]
2661    fn test_standard_expand_gesture() {
2662        let edit = |area, button, modifiers, click_count| {
2663            StandardExpandGesture.edit(&ExpandGestureContext::new(
2664                area,
2665                button,
2666                modifiers,
2667                click_count,
2668            ))
2669        };
2670        let (left, plain) = (MouseButton::Left, Modifiers::default());
2671        let alt = Modifiers::alt();
2672        // A chevron toggles on the first press of a series; with Alt, deep.
2673        assert_eq!(
2674            edit(ExpandArea::Chevron, left, plain, 1),
2675            Some(ExpandEdit::Toggle)
2676        );
2677        assert_eq!(
2678            edit(ExpandArea::Chevron, left, alt, 1),
2679            Some(ExpandEdit::ToggleDeep)
2680        );
2681        assert_eq!(edit(ExpandArea::Chevron, left, plain, 2), None);
2682        // A caption toggles on the second press of a double click.
2683        assert_eq!(edit(ExpandArea::Caption, left, plain, 1), None);
2684        assert_eq!(
2685            edit(ExpandArea::Caption, left, plain, 2),
2686            Some(ExpandEdit::Toggle)
2687        );
2688        assert_eq!(edit(ExpandArea::Caption, left, plain, 3), None);
2689        // A tree cell never: a double click confirms its row.
2690        assert_eq!(edit(ExpandArea::TreeCell, left, plain, 1), None);
2691        assert_eq!(edit(ExpandArea::TreeCell, left, plain, 2), None);
2692        // Other buttons never.
2693        assert_eq!(
2694            edit(ExpandArea::Chevron, MouseButton::Right, plain, 1),
2695            None
2696        );
2697        assert_eq!(
2698            edit(ExpandArea::Chevron, MouseButton::Middle, plain, 1),
2699            None
2700        );
2701
2702        let context = ExpandGestureContext::new(ExpandArea::TreeCell, left, alt, 2);
2703        assert_eq!(context.area(), ExpandArea::TreeCell);
2704        assert_eq!(context.button(), left);
2705        assert!(context.modifiers().alt);
2706        assert_eq!(context.click_count(), 2);
2707    }
2708
2709    #[test]
2710    fn test_focus_captions() {
2711        use std::{sync::Arc, time::Instant};
2712
2713        use crate::{
2714            engine::{GroupBy, ProjectionEngine as _, Rebuild, ViewSpec},
2715            highlight::HighlightPolicy,
2716            model::{CellValue, GridColumns},
2717            store::{Ingest, Op},
2718        };
2719
2720        #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2721        enum Col {
2722            Side,
2723            Qty,
2724        }
2725
2726        impl GridColumns for Col {
2727            fn all() -> &'static [Self] {
2728                &[Col::Side, Col::Qty]
2729            }
2730
2731            fn id(self) -> &'static str {
2732                match self {
2733                    Col::Side => "side",
2734                    Col::Qty => "qty",
2735                }
2736            }
2737
2738            fn ix(self) -> ColumnIx {
2739                ColumnIx::new(self as u16)
2740            }
2741        }
2742
2743        struct Row(u32, &'static str);
2744
2745        impl GridRow for Row {
2746            type Key = u32;
2747            type Column = Col;
2748
2749            fn key(&self) -> &u32 {
2750                &self.0
2751            }
2752
2753            fn value(&self, column: Col) -> CellValue<'_> {
2754                match column {
2755                    Col::Side => CellValue::Text(self.1),
2756                    Col::Qty => CellValue::Int(self.0.into()),
2757                }
2758            }
2759        }
2760
2761        // Six rows in two groups, Buy (even keys) and Sell (odd keys).
2762        let mut ingest = Ingest::new(HighlightPolicy::default(), Instant::now());
2763        ingest.apply(
2764            (0..6).map(|id| {
2765                let side = if id % 2 == 0 { "Buy" } else { "Sell" };
2766                Op::Upsert(Arc::new(Row(id, side)))
2767            }),
2768            Instant::now(),
2769        );
2770        let view = ViewSpec::new()
2771            .with_sort([(Col::Qty, SortDirection::Descending)])
2772            .with_group_by([GroupBy::column(Col::Side)]);
2773        let projection = Rebuild::default().rebuild(&ingest.snapshot(), &view);
2774        // Removed after the projection was built: left out of the scope.
2775        ingest.apply([Op::Remove(2)], Instant::now());
2776        let snapshot = ingest.snapshot();
2777        let buy = projection.groups().iter().next().expect("the Buy group");
2778        let rows = projection.order_ix(buy.rows()[0]).expect("in the order");
2779
2780        let policy: &dyn SelectionPolicy<Row> = &FocusCaptions;
2781        let caption = SelectableLine::Caption { group: buy };
2782        assert!(!policy.is_selectable(&caption));
2783        let context = LineScopeContext::new(
2784            caption,
2785            rows..rows + buy.rows().len(),
2786            &projection,
2787            &snapshot,
2788            SelectionMode::CellSet,
2789        );
2790        assert_eq!(context.mode(), SelectionMode::CellSet);
2791        assert_eq!(context.projection().len(), projection.len());
2792        assert_eq!(context.snapshot().len(), 5);
2793        // Every live row of the group in order, with every column, in the
2794        // cell modes too.
2795        let scope = policy.line_scope(&context).expect("a caption's scope");
2796        assert_eq!(scope.keys(), [4, 0]);
2797        assert_eq!(scope.columns(0), ColumnSet::first(2));
2798
2799        // A row is selectable, and a command over it acts on the selection.
2800        let row = snapshot.get(&3).expect("row 3");
2801        let line = SelectableLine::Row {
2802            row: &**row,
2803            branch: None,
2804        };
2805        assert!(policy.is_selectable(&line));
2806        let context =
2807            LineScopeContext::new(line, 0..0, &projection, &snapshot, SelectionMode::RowSet);
2808        assert_eq!(context.rows().count(), 0);
2809        assert!(policy.line_scope(&context).is_none());
2810    }
2811
2812    #[test]
2813    fn test_focus_branches_policy() {
2814        use std::{sync::Arc, time::Instant};
2815
2816        use crate::{
2817            engine::{ExpandDepth, GroupBy, ProjectionEngine as _, Rebuild, TreeSpec, ViewSpec},
2818            highlight::HighlightPolicy,
2819            model::{CellValue, GridColumns},
2820            store::{Ingest, Op},
2821        };
2822
2823        #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2824        enum Col {
2825            Id,
2826            Kind,
2827        }
2828
2829        impl GridColumns for Col {
2830            fn all() -> &'static [Self] {
2831                &[Col::Id, Col::Kind]
2832            }
2833
2834            fn id(self) -> &'static str {
2835                match self {
2836                    Col::Id => "id",
2837                    Col::Kind => "kind",
2838                }
2839            }
2840
2841            fn ix(self) -> ColumnIx {
2842                ColumnIx::new(self as u16)
2843            }
2844        }
2845
2846        struct Node {
2847            id: u32,
2848            parent: Option<u32>,
2849        }
2850
2851        impl GridRow for Node {
2852            type Key = u32;
2853            type Column = Col;
2854
2855            fn key(&self) -> &u32 {
2856                &self.id
2857            }
2858
2859            fn value(&self, column: Col) -> CellValue<'_> {
2860                match column {
2861                    Col::Id => CellValue::Int(self.id.into()),
2862                    Col::Kind => CellValue::Text(if self.id % 2 == 0 { "even" } else { "odd" }),
2863                }
2864            }
2865
2866            fn parent_key(&self) -> Option<&u32> {
2867                self.parent.as_ref()
2868            }
2869        }
2870
2871        // 0 ─┬─ 1 ─┬─ 3
2872        //    │     └─ 4
2873        //    ├─ 2 ─── 5   (2 collapsed)
2874        //    └─ 6
2875        // 7
2876        let parent = |id| match id {
2877            1 | 2 | 6 => Some(0),
2878            3 | 4 => Some(1),
2879            5 => Some(2),
2880            _ => None,
2881        };
2882        let mut ingest = Ingest::new(HighlightPolicy::default(), Instant::now());
2883        ingest.apply(
2884            (0..8).map(|id| {
2885                Op::Upsert(Arc::new(Node {
2886                    id,
2887                    parent: parent(id),
2888                }))
2889            }),
2890            Instant::now(),
2891        );
2892        let view = ViewSpec::new()
2893            .with_sort([(Col::Id, SortDirection::Ascending)])
2894            .with_tree(TreeSpec::new())
2895            .with_tree_expansion(ExpandDepth::All, [2]);
2896        let projection = Rebuild::default().rebuild(&ingest.snapshot(), &view);
2897        let snapshot = ingest.snapshot();
2898        let order: Vec<u32> = projection
2899            .rows()
2900            .iter()
2901            .map(|id| snapshot.row(*id).expect("live").id)
2902            .collect();
2903        assert_eq!(order, [0, 1, 3, 4, 2, 5, 6, 7]);
2904
2905        let policy: &dyn SelectionPolicy<Node> = &FocusBranches;
2906        let line = |key: u32| {
2907            let id = snapshot.row_id(&key).expect("a row");
2908            SelectableLine::Row {
2909                row: &**snapshot.row(id).expect("live"),
2910                branch: projection.branch(id),
2911            }
2912        };
2913        let scope = |key: u32, snapshot: &StoreSnapshot<Node>| {
2914            let id = snapshot.row_id(&key).expect("a row");
2915            let order_ix = projection.order_ix(id).expect("in the order");
2916            let under = projection
2917                .branch(id)
2918                .map_or(0, |branch| branch.descendant_count());
2919            let context = LineScopeContext::new(
2920                line(key),
2921                order_ix + 1..order_ix + 1 + under,
2922                &projection,
2923                snapshot,
2924                SelectionMode::CellSet,
2925            );
2926            policy.line_scope(&context)
2927        };
2928        // A branch is not selectable, a leaf is: 2 too, though collapsed.
2929        for (key, selectable) in [(0, false), (1, false), (2, false), (3, true), (6, true)] {
2930            assert_eq!(policy.is_selectable(&line(key)), selectable, "{key}");
2931        }
2932        assert!(FocusCaptions.is_selectable(&line(0)), "a node is a row");
2933        // A command over a branch: the leaves of its subtree in the order of
2934        // `rows()`, the one under the collapsed branch 2 too, without the
2935        // branches themselves; every column, in the cell modes too.
2936        let all = scope(0, &snapshot).expect("a branch's scope");
2937        assert_eq!(all.keys(), [3, 4, 5, 6]);
2938        assert_eq!(all.columns(0), ColumnSet::first(2));
2939        assert_eq!(scope(1, &snapshot).expect("a branch").keys(), [3, 4]);
2940        assert_eq!(scope(2, &snapshot).expect("a branch").keys(), [5]);
2941        // Over a leaf, the scoped selection.
2942        assert!(scope(3, &snapshot).is_none());
2943        assert!(scope(7, &snapshot).is_none());
2944        // A row removed since the projection was built is left out.
2945        ingest.apply([Op::Remove(4)], Instant::now());
2946        let after = ingest.snapshot();
2947        assert_eq!(scope(1, &after).expect("a branch").keys(), [3]);
2948
2949        // Group captions as with `FocusCaptions`: focus, no selection, the
2950        // group's rows.
2951        let grouped = Rebuild::default().rebuild(
2952            &snapshot,
2953            &ViewSpec::new()
2954                .with_sort([(Col::Id, SortDirection::Ascending)])
2955                .with_group_by([GroupBy::column(Col::Kind)]),
2956        );
2957        let even = grouped.groups().iter().next().expect("the even group");
2958        let start = grouped.order_ix(even.rows()[0]).expect("in the order");
2959        let caption = SelectableLine::Caption { group: even };
2960        assert!(!policy.is_selectable(&caption));
2961        let context = LineScopeContext::new(
2962            caption,
2963            start..start + even.rows().len(),
2964            &grouped,
2965            &snapshot,
2966            SelectionMode::RowSet,
2967        );
2968        assert_eq!(
2969            policy
2970                .line_scope(&context)
2971                .expect("a caption's scope")
2972                .keys(),
2973            [0, 2, 4, 6]
2974        );
2975        assert_eq!(context.lines().count(), 4);
2976    }
2977}