1use 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#[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: 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 pub(crate) fn with_menu_open(mut self, menu_open: bool) -> Self {
64 self.menu_open = menu_open;
65 self
66 }
67
68 pub(crate) fn with_tooltip_shown(mut self, tooltip_shown: bool) -> Self {
70 self.tooltip_shown = tooltip_shown;
71 self
72 }
73
74 pub(crate) fn with_editing(mut self, editing: bool) -> Self {
76 self.editing = editing;
77 self
78 }
79
80 pub(crate) fn with_pending_edits(mut self, pending_edits: bool) -> Self {
82 self.pending_edits = pending_edits;
83 self
84 }
85
86 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 pub(crate) fn with_drag_over(mut self, drag_over: bool) -> Self {
94 self.drag_over = drag_over;
95 self
96 }
97
98 pub fn now(&self) -> Instant {
100 self.now
101 }
102
103 pub fn held_since(&self) -> Instant {
105 self.held_since
106 }
107
108 pub fn is_pointer_inside(&self) -> bool {
110 self.pointer_inside
111 }
112
113 pub fn is_user_action(&self) -> bool {
116 self.user_action
117 }
118
119 pub fn is_menu_open(&self) -> bool {
123 self.menu_open
124 }
125
126 pub fn is_tooltip_shown(&self) -> bool {
131 self.tooltip_shown
132 }
133
134 pub fn is_editing(&self) -> bool {
138 self.editing
139 }
140
141 pub fn has_pending_edits(&self) -> bool {
145 self.pending_edits
146 }
147
148 pub fn is_drag_over(&self) -> bool {
151 self.drag_over
152 }
153
154 pub fn is_column_chooser_open(&self) -> bool {
157 self.column_chooser_open
158 }
159}
160
161#[derive(Clone, Copy, Debug, PartialEq, Eq)]
163pub enum ReorderDecision {
164 Apply,
166 HoldUntil(Instant),
168 Hold,
171}
172
173pub trait ReorderPolicy: 'static {
184 fn decide(&self, context: &ReorderContext) -> ReorderDecision;
188}
189
190#[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#[derive(Clone, Copy, Debug)]
209pub struct HoldReorder {
210 max: Duration,
211}
212
213impl HoldReorder {
214 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 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#[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
264pub trait TaskRunner: 'static {
270 fn run(&self, job: Box<dyn FnOnce() + Send>, cx: &App) -> Task<()>;
272}
273
274#[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#[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
295pub 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 pub fn zone(&self) -> ColumnZone {
320 self.zone
321 }
322
323 pub fn columns(&self) -> &'a [ColumnIx] {
325 self.columns
326 }
327
328 pub fn is_visible(&self, column: ColumnIx) -> bool {
330 self.visible.contains(column)
331 }
332
333 pub fn bands(&self, column: ColumnIx) -> &'a [&'static str] {
335 self.bands.get(column.get()).map_or(&[], |bands| bands)
336 }
337}
338
339pub 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 pub fn columns(&self) -> &[ColumnIx] {
363 self.order
364 }
365
366 pub fn zone(&self, column: ColumnIx) -> ColumnZone {
368 self.zones.get(column.get()).copied().unwrap_or_default()
369 }
370
371 pub fn bands(&self, column: ColumnIx) -> &'a [&'static str] {
373 self.bands.get(column.get()).map_or(&[], |bands| bands)
374 }
375
376 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 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
401pub trait ColumnMovePolicy: 'static {
408 fn is_allowed(&self, context: &ColumnMoveContext<'_>, block: Range<usize>, gap: usize) -> bool;
411
412 fn zone_block(&self, context: &ColumnMoveContext<'_>, column: ColumnIx) -> Range<usize>;
415
416 fn normalize(&self, _order: &mut ColumnOrder<'_>) {}
419}
420
421#[derive(Clone, Copy, Debug, Default)]
425pub struct BandedMove;
426
427#[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
441fn 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 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 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 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
522fn 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#[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
563pub struct AutofitContext<'a> {
566 header_width: Pixels,
567 pinned_width: Pixels,
568 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 pub(crate) fn with_pinned_width(mut self, width: Pixels) -> Self {
594 self.pinned_width = width;
595 self
596 }
597
598 pub fn header_width(&self) -> Pixels {
601 self.header_width
602 }
603
604 pub fn pinned_width(&self) -> Pixels {
607 self.pinned_width
608 }
609
610 pub(crate) fn with_summary_width(mut self, width: Pixels) -> Self {
613 self.summary_width = width;
614 self
615 }
616
617 pub fn summary_width(&self) -> Pixels {
623 self.summary_width
624 }
625
626 pub fn painted_rows(&self) -> Range<usize> {
628 self.painted_rows.clone()
629 }
630
631 pub fn row_count(&self) -> usize {
633 self.row_count
634 }
635
636 pub fn measure_row(&mut self, display_ix: usize) -> Option<Pixels> {
639 (self.measure)(display_ix)
640 }
641}
642
643pub trait AutofitPolicy: 'static {
649 fn content_width(&self, context: &mut AutofitContext<'_>) -> Pixels;
651}
652
653#[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#[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 pub fn sort(&self) -> &'a [(ColumnIx, SortDirection)] {
695 self.sort
696 }
697
698 pub fn column(&self) -> ColumnIx {
700 self.column
701 }
702
703 pub fn modifiers(&self) -> Modifiers {
705 self.modifiers
706 }
707
708 pub fn direction(&self) -> Option<SortDirection> {
710 self.sort
711 .iter()
712 .find(|(column, _)| *column == self.column)
713 .map(|(_, direction)| *direction)
714 }
715}
716
717pub trait SortCycle: 'static {
726 fn next_sort(&self, context: &SortCycleContext<'_>) -> Vec<(ColumnIx, SortDirection)>;
729}
730
731#[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
769pub 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 pub fn selection(&self) -> &'a GridSelection<R::Key> {
794 self.selection
795 }
796
797 pub fn projection(&self) -> &'a Projection {
799 self.projection
800 }
801
802 pub fn snapshot(&self) -> &'a StoreSnapshot<R> {
804 self.snapshot
805 }
806
807 pub fn mode(&self) -> SelectionMode {
809 self.mode
810 }
811}
812
813pub trait SelectionScope<R: GridRow>: 'static {
820 fn resolve(&self, context: &SelectionScopeContext<'_, R>) -> ScopedSelection<R::Key>;
822}
823
824#[derive(Clone, Copy, Debug, Default)]
828pub struct ProjectionScope;
829
830fn 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#[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#[non_exhaustive]
904pub enum SelectableLine<'a, R: GridRow> {
905 Row {
907 row: &'a R,
909 branch: Option<Branch>,
911 },
912 Caption {
914 group: GroupInfo<'a>,
916 },
917}
918
919pub struct LineScopeContext<'a, R: GridRow> {
923 line: SelectableLine<'a, R>,
924 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 pub fn line(&self) -> &SelectableLine<'a, R> {
950 &self.line
951 }
952
953 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 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 pub fn projection(&self) -> &'a Projection {
980 self.projection
981 }
982
983 pub fn snapshot(&self) -> &'a StoreSnapshot<R> {
985 self.snapshot
986 }
987
988 pub fn mode(&self) -> SelectionMode {
990 self.mode
991 }
992}
993
994pub trait SelectionPolicy<R: GridRow>: 'static {
1009 fn is_selectable(&self, line: &SelectableLine<'_, R>) -> bool;
1012
1013 fn line_scope(&self, context: &LineScopeContext<'_, R>) -> Option<ScopedSelection<R::Key>>;
1016}
1017
1018#[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#[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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1095#[non_exhaustive]
1096pub enum GestureInput {
1097 Keyboard,
1099 Pointer,
1101 ContextPointer,
1104}
1105
1106#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1108#[non_exhaustive]
1109pub enum SelectionEdit {
1110 Replace,
1112 Extend,
1114 AddRange,
1116 Toggle,
1118 Keep,
1120}
1121
1122#[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 pub fn input(&self) -> GestureInput {
1151 self.input
1152 }
1153
1154 pub fn modifiers(&self) -> Modifiers {
1156 self.modifiers
1157 }
1158
1159 pub fn is_extending(&self) -> bool {
1161 self.extending
1162 }
1163
1164 pub fn is_target_selected(&self) -> bool {
1167 self.target_selected
1168 }
1169
1170 pub fn mode(&self) -> SelectionMode {
1172 self.mode
1173 }
1174}
1175
1176pub trait SelectionGesture: 'static {
1185 fn edit(&self, context: &SelectionGestureContext) -> SelectionEdit;
1187}
1188
1189#[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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1218#[non_exhaustive]
1219pub enum GridArea {
1220 Body,
1222 Header,
1225}
1226
1227#[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 pub fn button(&self) -> MouseButton {
1253 self.button
1254 }
1255
1256 pub fn modifiers(&self) -> Modifiers {
1258 self.modifiers
1259 }
1260
1261 pub fn click_count(&self) -> usize {
1264 self.click_count
1265 }
1266
1267 pub fn area(&self) -> GridArea {
1269 self.area
1270 }
1271}
1272
1273pub trait ContextMenuGesture: 'static {
1286 fn opens_menu(&self, context: &ContextMenuGestureContext) -> bool;
1288}
1289
1290#[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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1310#[non_exhaustive]
1311pub enum ExpandArea {
1312 Chevron,
1314 Caption,
1316 TreeCell,
1318}
1319
1320#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1322#[non_exhaustive]
1323pub enum ExpandEdit {
1324 Toggle,
1326 ToggleDeep,
1328}
1329
1330#[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 pub fn area(&self) -> ExpandArea {
1356 self.area
1357 }
1358
1359 pub fn button(&self) -> MouseButton {
1361 self.button
1362 }
1363
1364 pub fn modifiers(&self) -> Modifiers {
1366 self.modifiers
1367 }
1368
1369 pub fn click_count(&self) -> usize {
1372 self.click_count
1373 }
1374}
1375
1376pub trait ExpandGesture: 'static {
1388 fn edit(&self, context: &ExpandGestureContext) -> Option<ExpandEdit>;
1390}
1391
1392#[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#[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 pub fn input(&self) -> &EditInput {
1437 &self.input
1438 }
1439
1440 pub fn modifiers(&self) -> Modifiers {
1442 self.modifiers
1443 }
1444
1445 pub fn content(&self) -> CellContent {
1448 self.content
1449 }
1450}
1451
1452#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1456#[non_exhaustive]
1457pub enum EditCommitKey {
1458 Enter,
1460 Up,
1462 Down,
1464 Next,
1466 Prev,
1468}
1469
1470#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1472#[non_exhaustive]
1473pub enum AfterCommit {
1474 Stay,
1476 Move(CellMove),
1478 MoveAndEdit(CellMove),
1481}
1482
1483#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1485#[non_exhaustive]
1486pub enum CellMove {
1487 Up,
1489 Down,
1491 Left,
1493 Right,
1495 NextEditable,
1497 PrevEditable,
1499}
1500
1501pub trait EditGesture: 'static {
1508 fn starts_edit(&self, context: &EditGestureContext) -> bool;
1512
1513 fn after_commit(&self, key: EditCommitKey) -> AfterCommit;
1515}
1516
1517#[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#[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 pub fn base(&self) -> &'a OwnedValue {
1570 self.base
1571 }
1572
1573 pub fn current(&self) -> &'a OwnedValue {
1575 self.current
1576 }
1577
1578 pub fn draft(&self) -> Option<&'a OwnedValue> {
1580 self.draft
1581 }
1582
1583 pub fn is_dirty(&self) -> bool {
1585 self.dirty
1586 }
1587}
1588
1589#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1591#[non_exhaustive]
1592pub enum EditEnd {
1593 Commit,
1595 Cancel,
1597}
1598
1599#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1602#[non_exhaustive]
1603pub enum DraftAction {
1604 Keep,
1606 Replace,
1608 Cancel,
1610}
1611
1612pub trait EditPolicy: 'static {
1617 fn on_blur(&self, context: &EditPolicyContext<'_>) -> EditEnd;
1619
1620 fn commit_unchanged(&self) -> bool {
1624 false
1625 }
1626
1627 fn on_value_change(&self, context: &EditPolicyContext<'_>) -> DraftAction;
1629
1630 fn on_hidden(&self, context: &EditPolicyContext<'_>) -> EditEnd;
1633
1634 fn settle_timeout(&self) -> Duration {
1637 Duration::from_secs(5)
1638 }
1639
1640 fn error_mark_timeout(&self) -> Option<Duration> {
1644 None
1645 }
1646}
1647
1648#[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#[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
1698pub 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 pub fn line(&self) -> &SelectableLine<'a, R> {
1732 &self.line
1733 }
1734
1735 pub fn column(&self) -> R::Column {
1738 self.column
1739 }
1740
1741 pub fn was_selected(&self) -> bool {
1743 self.was_selected
1744 }
1745
1746 pub fn modifiers(&self) -> Modifiers {
1748 self.modifiers
1749 }
1750
1751 pub fn mode(&self) -> SelectionMode {
1753 self.mode
1754 }
1755
1756 pub fn click_count(&self) -> usize {
1759 self.click_count
1760 }
1761}
1762
1763pub trait RowDragGesture<R: GridRow>: 'static {
1770 fn is_drag_press(&self, press: &RowDragPressContext<'_, R>) -> bool;
1772
1773 fn threshold(&self) -> Pixels {
1776 px(10.)
1777 }
1778
1779 fn is_enabled(&self) -> bool {
1782 true
1783 }
1784}
1785
1786#[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#[derive(Clone, Copy, Debug)]
1807pub struct SelectedRowsDrag {
1808 threshold: Pixels,
1809}
1810
1811impl SelectedRowsDrag {
1812 pub fn new() -> Self {
1814 Self { threshold: px(10.) }
1815 }
1816
1817 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#[non_exhaustive]
1850pub enum DropLine<'a, R: GridRow> {
1851 Row {
1853 key: &'a R::Key,
1855 branch: Option<Branch>,
1857 },
1858 Caption(GroupInfo<'a>),
1860 Empty,
1862}
1863
1864pub 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 pub fn line(&self) -> &DropLine<'a, R> {
1909 &self.line
1910 }
1911
1912 pub fn offset(&self) -> f32 {
1915 self.offset
1916 }
1917
1918 pub fn rows(&self) -> Option<&'a RowDrag<R::Key>> {
1921 self.rows
1922 }
1923
1924 pub fn payload<T: 'static>(&self) -> Option<&'a T> {
1926 self.value?.downcast_ref()
1927 }
1928
1929 pub fn is_own(&self) -> bool {
1931 self.own
1932 }
1933
1934 pub fn modifiers(&self) -> Modifiers {
1936 self.modifiers
1937 }
1938
1939 pub fn is_sorted(&self) -> bool {
1942 self.sorted
1943 }
1944
1945 pub fn is_grouped(&self) -> bool {
1947 self.grouped
1948 }
1949
1950 pub fn is_tree(&self) -> bool {
1952 self.tree
1953 }
1954
1955 pub fn last_row(&self) -> Option<&'a R::Key> {
1958 self.last_row
1959 }
1960
1961 pub fn row(&self, key: &R::Key) -> Option<&'a Arc<R>> {
1963 self.snapshot.row(self.snapshot.row_id(key)?)
1964 }
1965}
1966
1967pub trait RowDropPolicy<R: GridRow>: 'static {
1974 fn place(&self, drop: &RowDropContext<'_, R>) -> Option<DropTarget<R::Key>>;
1976
1977 fn is_enabled(&self) -> bool {
1980 true
1981 }
1982
1983 fn is_copy(&self, modifiers: Modifiers) -> bool {
1989 modifiers.alt || modifiers.control
1990 }
1991}
1992
1993#[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#[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#[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 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 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 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 assert!(allowed(2..4, 1));
2105 assert!(allowed(2..4, 5));
2106 assert!(!allowed(2..4, 0));
2107 assert!(allowed(1..5, 0));
2109 assert!(allowed(1..5, 6));
2110 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 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 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 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 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 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 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 (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 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 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 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 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 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 assert_eq!(next(&[(ix(1), Descending)], 2, false), [(ix(2), Ascending)]);
2213 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 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 assert_eq!(
2231 next(&multi, 1, true),
2232 [(ix(1), Descending), (ix(2), Ascending), (ix(3), Descending)]
2233 );
2234 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 assert_eq!(resolve(&ProjectionScope, &few), [9, 1]);
2642 assert_eq!(resolve(&ProjectionScope, &many), [9, 7, 3, 1]);
2643 assert!(resolve(&ProjectionScope, &[]).is_empty());
2644 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 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 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 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 assert_eq!(edit(ExpandArea::TreeCell, left, plain, 1), None);
2691 assert_eq!(edit(ExpandArea::TreeCell, left, plain, 2), None);
2692 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 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 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 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 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 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 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 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 assert!(scope(3, &snapshot).is_none());
2943 assert!(scope(7, &snapshot).is_none());
2944 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 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}