1use std::{ops::Range, sync::Arc};
10
11use gpui_kit::component::IconNamed;
12use gpui_kit::{App, Context, Pixels, Point, SharedString, Window, px};
13
14use super::{
15 CELL_PADDING, ColumnOf, LiveGridDelegate, LiveGridState, SelectionOrigin,
16 disclosure::INDENT,
17 events::{LayoutOrigin, LayoutPart, LayoutParts},
18 frame::{LEAD_ICON_LANE, cell_icon_size, cell_image_size},
19 header::{HEADER_SORT_LANE, header_icon_lane, header_label_style},
20 images::ImageFit,
21 pinned::{BodyLine, EDGES},
22 style::AggregateLine,
23 summary::{SummaryLine, SummaryMeasure, TOTALS_WEIGHT},
24};
25use crate::{
26 actions::{
27 AutofitColumn, AutofitColumns, FreezeColumnLeft, FreezeColumnRight, HideColumn,
28 MoveColumnLeft, MoveColumnRight, ShowAllColumns, UnfreezeColumn,
29 },
30 engine::DisplayRow,
31 model::{CellValue, ColumnIx, ColumnSet, GridColumns, GridRow, NumberFormat, display_text_in},
32 policy::{AutofitContext, AutofitPolicy, ColumnMoveContext, ColumnMovePolicy, ColumnOrder},
33};
34
35pub const MIN_COLUMN_WIDTH: Pixels = px(10.);
37
38#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
40pub enum ColumnAlign {
41 #[default]
43 Auto,
44 Start,
46 End,
48}
49
50#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
52pub enum ColumnZone {
53 FrozenLeft,
55 #[default]
57 Scrolling,
58 FrozenRight,
60}
61
62impl ColumnZone {
63 pub(crate) const ALL: [Self; 3] = [Self::FrozenLeft, Self::Scrolling, Self::FrozenRight];
65
66 pub(crate) const fn slot(self) -> usize {
68 self as usize
69 }
70}
71
72#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
76pub struct ViewColumnIx(u16);
77
78impl ViewColumnIx {
79 pub const fn new(ix: u16) -> Self {
81 Self(ix)
82 }
83
84 pub const fn get(self) -> usize {
86 self.0 as usize
87 }
88}
89
90#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
92#[non_exhaustive]
93pub enum CellContent {
94 #[default]
98 Text,
99 Element,
104 Control,
109}
110
111impl CellContent {
112 pub(crate) const fn key_value(self) -> &'static str {
116 match self {
117 Self::Text => "text",
118 Self::Element => "element",
119 Self::Control => "control",
120 }
121 }
122}
123
124#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
128#[non_exhaustive]
129pub enum HeaderLabel {
130 #[default]
132 Title,
133 IconAndTitle(SharedString),
135 Icon(SharedString),
137}
138
139#[derive(Clone, Debug)]
141pub struct GridColumn<C> {
142 pub(super) column: C,
143 pub(super) title: SharedString,
144 pub(super) width: Pixels,
145 pub(super) align: ColumnAlign,
146 pub(super) min_width: Pixels,
147 pub(super) max_width: Pixels,
148 pub(super) zone: ColumnZone,
149 pub(super) visible: bool,
150 pub(super) resizable: bool,
151 pub(super) movable: bool,
152 pub(super) sortable: bool,
153 pub(super) groupable: bool,
154 pub(super) content: CellContent,
156 pub(super) header_label: HeaderLabel,
157 pub(super) hideable: bool,
158 pub(super) internal: bool,
160 pub(super) number_format: NumberFormat,
162 pub(super) image_fit: ImageFit,
164 pub(super) image_corner_radius: Pixels,
165}
166
167impl<C: GridColumns> GridColumn<C> {
168 pub fn new(column: C, title: impl Into<SharedString>) -> Self {
170 Self {
171 column,
172 title: title.into(),
173 width: px(100.),
174 align: ColumnAlign::Auto,
175 min_width: MIN_COLUMN_WIDTH,
176 max_width: Pixels::MAX,
177 zone: ColumnZone::Scrolling,
178 visible: true,
179 resizable: true,
180 movable: true,
181 sortable: true,
182 groupable: true,
183 content: CellContent::Text,
184 header_label: HeaderLabel::Title,
185 hideable: true,
186 internal: false,
187 number_format: NumberFormat::new(),
188 image_fit: ImageFit::Contain,
189 image_corner_radius: px(0.),
190 }
191 }
192
193 pub fn with_width(mut self, width: Pixels) -> Self {
195 self.width = width;
196 self
197 }
198
199 pub fn with_align(mut self, align: ColumnAlign) -> Self {
201 self.align = align;
202 self
203 }
204
205 pub fn with_min_width(mut self, width: Pixels) -> Self {
207 self.min_width = width.max(MIN_COLUMN_WIDTH);
208 self
209 }
210
211 pub fn with_max_width(mut self, width: Pixels) -> Self {
213 self.max_width = width;
214 self
215 }
216
217 pub fn with_zone(mut self, zone: ColumnZone) -> Self {
219 self.zone = zone;
220 self
221 }
222
223 pub fn with_visible(mut self, visible: bool) -> Self {
225 self.visible = visible;
226 self
227 }
228
229 pub fn with_resizable(mut self, resizable: bool) -> Self {
231 self.resizable = resizable;
232 self
233 }
234
235 pub fn with_movable(mut self, movable: bool) -> Self {
237 self.movable = movable;
238 self
239 }
240
241 pub fn with_sortable(mut self, sortable: bool) -> Self {
244 self.sortable = sortable;
245 self
246 }
247
248 pub fn with_groupable(mut self, groupable: bool) -> Self {
253 self.groupable = groupable;
254 self
255 }
256
257 pub fn with_content(mut self, content: CellContent) -> Self {
260 self.content = content;
261 self
262 }
263
264 pub fn with_icon(mut self, icon: impl IconNamed) -> Self {
266 self.header_label = HeaderLabel::IconAndTitle(icon.path());
267 self
268 }
269
270 pub fn with_icon_only(mut self, icon: impl IconNamed) -> Self {
274 self.header_label = HeaderLabel::Icon(icon.path());
275 self
276 }
277
278 pub fn with_hideable(mut self, hideable: bool) -> Self {
283 self.hideable = hideable;
284 self
285 }
286
287 pub fn with_internal(mut self, internal: bool) -> Self {
295 self.internal = internal;
296 self
297 }
298
299 pub fn with_number_format(mut self, format: NumberFormat) -> Self {
303 self.number_format = format;
304 self
305 }
306
307 pub fn with_image_fit(mut self, fit: ImageFit) -> Self {
310 self.image_fit = fit;
311 self
312 }
313
314 pub fn with_image_corner_radius(mut self, radius: Pixels) -> Self {
317 self.image_corner_radius = radius;
318 self
319 }
320
321 pub fn column(&self) -> C {
323 self.column
324 }
325
326 pub fn title(&self) -> &SharedString {
328 &self.title
329 }
330
331 pub fn width(&self) -> Pixels {
333 self.width
334 }
335
336 pub fn min_width(&self) -> Pixels {
338 self.min_width
339 }
340
341 pub fn max_width(&self) -> Pixels {
343 self.max_width
344 }
345
346 pub fn align(&self) -> ColumnAlign {
348 self.align
349 }
350
351 pub fn zone(&self) -> ColumnZone {
353 self.zone
354 }
355
356 pub fn is_visible(&self) -> bool {
358 self.visible
359 }
360
361 pub fn is_resizable(&self) -> bool {
363 self.resizable
364 }
365
366 pub fn is_movable(&self) -> bool {
368 self.movable
369 }
370
371 pub fn is_sortable(&self) -> bool {
373 self.sortable
374 }
375
376 pub fn is_groupable(&self) -> bool {
378 self.groupable
379 }
380
381 pub fn content(&self) -> CellContent {
383 self.content
384 }
385
386 pub fn header_label(&self) -> &HeaderLabel {
389 &self.header_label
390 }
391
392 pub fn icon(&self) -> Option<&SharedString> {
394 match &self.header_label {
395 HeaderLabel::Title => None,
396 HeaderLabel::IconAndTitle(icon) | HeaderLabel::Icon(icon) => Some(icon),
397 }
398 }
399
400 pub fn is_hideable(&self) -> bool {
402 self.hideable
403 }
404
405 pub fn number_format(&self) -> NumberFormat {
407 self.number_format
408 }
409
410 pub fn is_internal(&self) -> bool {
412 self.internal
413 }
414
415 pub fn image_fit(&self) -> ImageFit {
417 self.image_fit
418 }
419
420 pub fn image_corner_radius(&self) -> Pixels {
422 self.image_corner_radius
423 }
424
425 pub(crate) fn clamp_width(&self, width: Pixels) -> Pixels {
428 let min = self.min_width.max(MIN_COLUMN_WIDTH);
429 if !f32::from(width).is_finite() || width <= px(0.) {
430 return min;
431 }
432 width.min(self.max_width).max(min)
433 }
434}
435
436#[derive(Clone, Debug)]
439pub struct ColumnBand<C> {
440 id: &'static str,
441 title: SharedString,
442 children: Vec<BandChild<C>>,
443}
444
445#[derive(Clone, Debug)]
446enum BandChild<C> {
447 Column(GridColumn<C>),
448 Band(ColumnBand<C>),
449}
450
451impl<C: GridColumns> ColumnBand<C> {
452 pub fn new(id: &'static str, title: impl Into<SharedString>) -> Self {
455 Self {
456 id,
457 title: title.into(),
458 children: Vec::new(),
459 }
460 }
461
462 pub fn column(mut self, column: GridColumn<C>) -> Self {
464 self.children.push(BandChild::Column(column));
465 self
466 }
467
468 pub fn band(mut self, band: ColumnBand<C>) -> Self {
470 self.children.push(BandChild::Band(band));
471 self
472 }
473
474 pub fn id(&self) -> &'static str {
476 self.id
477 }
478
479 pub fn title(&self) -> &SharedString {
481 &self.title
482 }
483}
484
485#[derive(Clone, Debug)]
488pub struct GridSchema<C> {
489 columns: Vec<GridColumn<C>>,
491 chains: Vec<Box<[&'static str]>>,
493 bands: Vec<(&'static str, SharedString)>,
495 frozen: usize,
496}
497
498impl<C: GridColumns> Default for GridSchema<C> {
499 fn default() -> Self {
500 Self {
501 columns: Vec::new(),
502 chains: Vec::new(),
503 bands: Vec::new(),
504 frozen: 0,
505 }
506 }
507}
508
509impl<C: GridColumns> GridSchema<C> {
510 pub fn new() -> Self {
512 Self::default()
513 }
514
515 pub fn column(mut self, column: GridColumn<C>) -> Self {
522 self.push_column(column, &[]);
523 self
524 }
525
526 pub fn band(mut self, band: ColumnBand<C>) -> Self {
532 self.push_band(band, &mut Vec::new());
533 self
534 }
535
536 pub fn with_frozen(mut self, count: usize) -> Self {
539 self.frozen = count;
540 self
541 }
542
543 fn push_column(&mut self, column: GridColumn<C>, chain: &[&'static str]) {
544 let ix = column.column.ix();
545 assert!(ix.get() < 128, "a grid supports at most 128 columns");
546 assert!(
547 !self
548 .columns
549 .iter()
550 .any(|c| c.column == column.column || c.column.id() == column.column.id()),
551 "column {:?} (id {:?}) is already in the schema",
552 column.column,
553 column.column.id(),
554 );
555 self.columns.push(column);
556 self.chains.push(chain.into());
557 }
558
559 fn push_band(&mut self, band: ColumnBand<C>, chain: &mut Vec<&'static str>) {
560 assert!(
561 !self.bands.iter().any(|(id, _)| *id == band.id),
562 "band {:?} is already in the schema",
563 band.id,
564 );
565 self.bands.push((band.id, band.title));
566 chain.push(band.id);
567 for child in band.children {
568 match child {
569 BandChild::Column(column) => self.push_column(column, chain),
570 BandChild::Band(band) => self.push_band(band, chain),
571 }
572 }
573 chain.pop();
574 }
575}
576
577#[derive(Clone, Debug, Default)]
579pub(crate) struct ZoneColumns {
580 pub(crate) columns: Vec<ColumnIx>,
581 pub(crate) offsets: Vec<Pixels>,
584}
585
586impl ZoneColumns {
587 pub(crate) fn len(&self) -> usize {
588 self.columns.len()
589 }
590
591 pub(crate) fn content_width(&self) -> Pixels {
593 self.offsets.last().copied().unwrap_or_default()
594 }
595
596 pub(crate) fn width(&self, i: usize) -> Pixels {
598 self.offsets[i + 1] - self.offsets[i]
599 }
600
601 pub(crate) fn range(&self, left: Pixels, right: Pixels) -> Range<usize> {
604 let n = self.len();
605 if n == 0 || right <= left {
606 return 0..0;
607 }
608 let start = self.offsets[1..].partition_point(|&end| end <= left);
609 let end = self.offsets[..n].partition_point(|&start| start < right);
610 start..end.max(start)
611 }
612
613 pub(crate) fn index_at(&self, x: Pixels) -> Option<usize> {
615 let ix = self.offsets.partition_point(|&o| o <= x);
616 (ix >= 1 && ix <= self.len()).then(|| ix - 1)
617 }
618}
619
620#[derive(Clone, Debug)]
623pub(crate) struct BandRun {
624 pub(crate) band: &'static str,
625 pub(crate) title: SharedString,
626 pub(crate) zone: ColumnZone,
627 pub(crate) level: usize,
629 pub(crate) columns: Range<usize>,
631 pub(crate) movable: bool,
633}
634
635#[derive(Clone, Debug)]
637pub(crate) struct ZoneFrame {
638 pub(crate) zone: ColumnZone,
639 pub(crate) x: Pixels,
641 pub(crate) width: Pixels,
643 pub(crate) scroll: Pixels,
645 pub(crate) columns: Range<usize>,
648}
649
650impl ZoneFrame {
651 pub(crate) fn column_x(&self, zone: &ZoneColumns, i: usize) -> Pixels {
653 self.x + zone.offsets[i] - self.scroll
654 }
655
656 pub(crate) fn right(&self) -> Pixels {
658 self.x + self.width
659 }
660}
661
662#[derive(Clone, Debug)]
664pub(crate) struct ZoneGeometry {
665 pub(crate) zones: [ZoneFrame; 3],
666}
667
668impl ZoneGeometry {
669 pub(crate) fn zone(&self, zone: ColumnZone) -> &ZoneFrame {
670 &self.zones[zone.slot()]
671 }
672
673 pub(crate) fn zone_at(&self, x: Pixels) -> Option<ColumnZone> {
676 let [left, scrolling, right] = &self.zones;
677 if right.width > px(0.) && x >= right.x && x < right.right() {
678 Some(ColumnZone::FrozenRight)
679 } else if left.width > px(0.) && x >= left.x && x < left.right() {
680 Some(ColumnZone::FrozenLeft)
681 } else if scrolling.width > px(0.) && x >= scrolling.x && x < scrolling.right() {
682 Some(ColumnZone::Scrolling)
683 } else {
684 None
685 }
686 }
687}
688
689#[derive(Clone, Copy, Debug, PartialEq, Eq)]
691pub(crate) enum Boundary {
692 Gap(usize),
694 Here,
696 Refused,
698}
699
700pub(crate) struct ZoneMove<'a> {
702 context: ColumnMoveContext<'a>,
703 members: Vec<ColumnIx>,
704 span: Range<usize>,
706 contiguous: bool,
707 visible_at: Vec<usize>,
709}
710
711impl ZoneMove<'_> {
712 pub(crate) fn resolve(&self, k: usize, policy: &dyn ColumnMovePolicy) -> Boundary {
717 let n = self.visible_at.len();
718 let check = |gap: usize| {
719 if self.contiguous && self.span.start <= gap && gap <= self.span.end {
720 Some(Boundary::Here)
721 } else if policy.is_allowed(&self.context, self.span.clone(), gap) {
722 Some(Boundary::Gap(gap))
723 } else {
724 None
725 }
726 };
727 let found = if k < n {
728 let low = if k > 0 { self.visible_at[k - 1] + 1 } else { 0 };
729 (low..=self.visible_at[k]).rev().find_map(check)
730 } else if k == n {
731 let low = n.checked_sub(1).map_or(0, |last| self.visible_at[last] + 1);
732 (low..=self.context.columns().len()).find_map(check)
733 } else {
734 None
735 };
736 found.unwrap_or(Boundary::Refused)
737 }
738}
739
740#[derive(Clone)]
744pub(crate) struct ColumnLayout<C> {
745 schema: Vec<Option<GridColumn<C>>>,
747 declared: Vec<(ColumnIx, ColumnZone)>,
749 chains: Vec<Box<[&'static str]>>,
751 bands: Vec<(&'static str, SharedString)>,
752 depth: usize,
754 order: Vec<ColumnIx>,
756 ends: [usize; 3],
758 widths: Vec<Pixels>,
759 zones: Vec<ColumnZone>,
760 visible: ColumnSet,
761 internal: ColumnSet,
763 zone_columns: [ZoneColumns; 3],
765 zone_starts: [usize; 3],
767 view_ix: Vec<u16>,
768 runs: Vec<BandRun>,
769}
770
771const NO_VIEW_IX: u16 = u16::MAX;
772
773impl<C: GridColumns> ColumnLayout<C> {
774 pub(crate) fn new(schema: GridSchema<C>) -> Self {
775 let count = C::all().len();
776 let mut columns: Vec<Option<GridColumn<C>>> = (0..count).map(|_| None).collect();
777 let mut chains: Vec<Box<[&'static str]>> = vec![Box::default(); count];
778 let mut widths = vec![px(0.); count];
779 let mut zones = vec![ColumnZone::Scrolling; count];
780 let mut visible = ColumnSet::EMPTY;
781 let mut internal = ColumnSet::EMPTY;
782 let mut declared = Vec::with_capacity(schema.columns.len());
783 let depth = schema.chains.iter().map(|chain| chain.len()).max();
784 for (position, (column, chain)) in schema.columns.into_iter().zip(schema.chains).enumerate()
785 {
786 let ix = column.column.ix();
787 let zone = if position < schema.frozen {
788 ColumnZone::FrozenLeft
789 } else {
790 column.zone
791 };
792 widths[ix.get()] = column.clamp_width(column.width);
793 zones[ix.get()] = zone;
794 if column.internal {
795 internal.insert(ix);
796 } else if column.visible {
797 visible.insert(ix);
798 }
799 chains[ix.get()] = chain;
800 declared.push((ix, zone));
801 columns[ix.get()] = Some(column);
802 }
803 let mut layout = Self {
804 schema: columns,
805 declared,
806 chains,
807 bands: schema.bands,
808 depth: depth.unwrap_or(0),
809 order: Vec::new(),
810 ends: [0; 3],
811 widths,
812 zones,
813 visible,
814 internal,
815 zone_columns: Default::default(),
816 zone_starts: [0; 3],
817 view_ix: vec![NO_VIEW_IX; count],
818 runs: Vec::new(),
819 };
820 layout.reset_order();
821 layout
822 }
823
824 fn reset_order(&mut self) {
826 self.order.clear();
827 for &(ix, zone) in &self.declared {
828 self.order.push(ix);
829 self.zones[ix.get()] = zone;
830 }
831 self.settle();
832 if self.visible.is_empty()
834 && let Some(&first) = self.order.iter().find(|ix| !self.internal.contains(**ix))
835 {
836 self.visible.insert(first);
837 self.settle();
838 }
839 }
840
841 pub(crate) fn reset(&mut self, policy: &dyn ColumnMovePolicy) {
843 self.reset_order();
844 self.normalize(policy);
845 }
846
847 pub(crate) fn normalize(&mut self, policy: &dyn ColumnMovePolicy) {
849 policy.normalize(&mut ColumnOrder::new(
850 &mut self.order,
851 &mut self.zones,
852 &self.chains,
853 ));
854 self.settle();
855 }
856
857 fn settle(&mut self) {
860 let zones = &self.zones;
861 self.order.sort_by_key(|ix| zones[ix.get()].slot());
862 for zone in ColumnZone::ALL {
863 self.ends[zone.slot()] = self
864 .order
865 .partition_point(|ix| zones[ix.get()].slot() <= zone.slot());
866 }
867 self.view_ix.fill(NO_VIEW_IX);
868 let mut view = 0;
869 for zone in ColumnZone::ALL {
870 let range = self.zone_range(zone);
871 let columns = &mut self.zone_columns[zone.slot()];
872 columns.columns.clear();
873 columns.offsets.clear();
874 self.zone_starts[zone.slot()] = view;
875 let mut x = px(0.);
876 columns.offsets.push(x);
877 for &ix in &self.order[range] {
878 if self.visible.contains(ix) {
879 columns.columns.push(ix);
880 x += self.widths[ix.get()];
881 columns.offsets.push(x);
882 self.view_ix[ix.get()] = view as u16;
883 view += 1;
884 }
885 }
886 }
887 self.runs.clear();
888 for zone in ColumnZone::ALL {
889 let columns = &self.zone_columns[zone.slot()];
890 for level in 0..self.depth {
891 let mut i = 0;
892 while i < columns.len() {
893 let Some(&band) = self.chains[columns.columns[i].get()].get(level) else {
894 i += 1;
895 continue;
896 };
897 let start = i;
898 while i < columns.len()
899 && self.chains[columns.columns[i].get()].get(level) == Some(&band)
900 {
901 i += 1;
902 }
903 let title = self
904 .bands
905 .iter()
906 .find(|(id, _)| *id == band)
907 .map(|(_, title)| title.clone())
908 .unwrap_or_default();
909 let movable = columns.columns[start..i]
910 .iter()
911 .all(|ix| self.column(*ix).movable);
912 self.runs.push(BandRun {
913 band,
914 title,
915 zone,
916 level,
917 columns: start..i,
918 movable,
919 });
920 }
921 }
922 }
923 }
924
925 fn zone_range(&self, zone: ColumnZone) -> Range<usize> {
926 let start = match zone.slot() {
927 0 => 0,
928 slot => self.ends[slot - 1],
929 };
930 start..self.ends[zone.slot()]
931 }
932
933 pub(crate) fn get(&self, ix: ColumnIx) -> Option<&GridColumn<C>> {
935 self.schema.get(ix.get()).and_then(Option::as_ref)
936 }
937
938 pub(crate) fn column(&self, ix: ColumnIx) -> &GridColumn<C> {
940 self.get(ix).expect("the column is in the schema")
941 }
942
943 pub(crate) fn order(&self) -> &[ColumnIx] {
945 &self.order
946 }
947
948 pub(crate) fn zone_order(&self, zone: ColumnZone) -> &[ColumnIx] {
950 &self.order[self.zone_range(zone)]
951 }
952
953 pub(crate) fn width(&self, ix: ColumnIx) -> Pixels {
954 self.widths.get(ix.get()).copied().unwrap_or_default()
955 }
956
957 pub(crate) fn is_visible(&self, ix: ColumnIx) -> bool {
958 self.visible.contains(ix)
959 }
960
961 pub(crate) fn zone_of(&self, ix: ColumnIx) -> ColumnZone {
962 self.zones.get(ix.get()).copied().unwrap_or_default()
963 }
964
965 pub(crate) fn visible_set(&self) -> ColumnSet {
967 self.visible
968 }
969
970 #[inline]
973 pub(crate) fn number_format(&self, ix: ColumnIx) -> NumberFormat {
974 self.schema
975 .get(ix.get())
976 .and_then(Option::as_ref)
977 .map_or_else(NumberFormat::new, |column| column.number_format)
978 }
979
980 pub(crate) fn number_formats(&self) -> Arc<[NumberFormat]> {
983 (0..self.schema.len())
984 .map(|ix| self.number_format(ColumnIx::new(ix as u16)))
985 .collect()
986 }
987
988 pub(crate) fn is_internal(&self, ix: ColumnIx) -> bool {
990 self.internal.contains(ix)
991 }
992
993 pub(crate) fn any_hidden(&self) -> bool {
996 self.order
997 .iter()
998 .any(|ix| !self.visible.contains(*ix) && !self.internal.contains(*ix))
999 }
1000
1001 pub(crate) fn visible(&self) -> impl Iterator<Item = ColumnIx> + '_ {
1003 self.zone_columns
1004 .iter()
1005 .flat_map(|zone| zone.columns.iter().copied())
1006 }
1007
1008 pub(crate) fn view_column_ix(&self, ix: ColumnIx) -> Option<ViewColumnIx> {
1009 let view = *self.view_ix.get(ix.get())?;
1010 (view != NO_VIEW_IX).then_some(ViewColumnIx(view))
1011 }
1012
1013 pub(crate) fn column_at(&self, view: ViewColumnIx) -> Option<ColumnIx> {
1014 let mut view = view.get();
1015 for zone in &self.zone_columns {
1016 if view < zone.len() {
1017 return Some(zone.columns[view]);
1018 }
1019 view -= zone.len();
1020 }
1021 None
1022 }
1023
1024 pub(crate) fn zone_position(&self, ix: ColumnIx) -> Option<(ColumnZone, usize)> {
1027 let view = self.view_column_ix(ix)?.get();
1028 let zone = self.zone_of(ix);
1029 Some((zone, view - self.zone_starts[zone.slot()]))
1030 }
1031
1032 pub(crate) fn zone_columns(&self, zone: ColumnZone) -> &ZoneColumns {
1033 &self.zone_columns[zone.slot()]
1034 }
1035
1036 pub(crate) fn runs(&self) -> &[BandRun] {
1038 &self.runs
1039 }
1040
1041 pub(crate) fn depth(&self) -> usize {
1043 self.depth
1044 }
1045
1046 pub(crate) fn bands_of(&self, ix: ColumnIx) -> &[&'static str] {
1048 self.chains.get(ix.get()).map_or(&[], |chain| chain)
1049 }
1050
1051 pub(crate) fn band_title(&self, id: &str) -> Option<&SharedString> {
1054 self.bands
1055 .iter()
1056 .find(|(band, _)| *band == id)
1057 .map(|(_, title)| title)
1058 }
1059
1060 pub(crate) fn total_width(&self) -> Pixels {
1062 self.zone_columns
1063 .iter()
1064 .map(ZoneColumns::content_width)
1065 .fold(px(0.), |total, width| total + width)
1066 }
1067
1068 pub(crate) fn geometry(&self, viewport_width: Pixels, scroll_x: Pixels) -> ZoneGeometry {
1073 let viewport = viewport_width.max(px(0.));
1074 let [left, scrolling, right] = &self.zone_columns;
1075 let left_width = left.content_width().min(viewport);
1076 let right_width = right.content_width().min(viewport - left_width);
1077 let scroll_width = (viewport - left_width - right_width).max(px(0.));
1078 ZoneGeometry {
1079 zones: [
1080 ZoneFrame {
1081 zone: ColumnZone::FrozenLeft,
1082 x: px(0.),
1083 width: left_width,
1084 scroll: px(0.),
1085 columns: left.range(px(0.), left_width),
1086 },
1087 ZoneFrame {
1088 zone: ColumnZone::Scrolling,
1089 x: left_width,
1090 width: scroll_width,
1091 scroll: scroll_x,
1092 columns: scrolling.range(scroll_x, scroll_x + scroll_width),
1093 },
1094 ZoneFrame {
1095 zone: ColumnZone::FrozenRight,
1096 x: viewport - right_width,
1097 width: right_width,
1098 scroll: px(0.),
1099 columns: right.range(px(0.), right_width),
1100 },
1101 ],
1102 }
1103 }
1104
1105 pub(crate) fn column_at_x(
1108 &self,
1109 x: Pixels,
1110 viewport_width: Pixels,
1111 scroll_x: Pixels,
1112 ) -> Option<ColumnIx> {
1113 let geometry = self.geometry(viewport_width, scroll_x);
1114 let zone = geometry.zone_at(x)?;
1115 let frame = geometry.zone(zone);
1116 let columns = self.zone_columns(zone);
1117 columns
1118 .index_at(x - frame.x + frame.scroll)
1119 .map(|i| columns.columns[i])
1120 }
1121
1122 pub(crate) fn move_context(&self, zone: ColumnZone) -> ColumnMoveContext<'_> {
1123 ColumnMoveContext::new(zone, self.zone_order(zone), self.visible, &self.chains)
1124 }
1125
1126 pub(crate) fn zone_move(&self, zone: ColumnZone, members: Vec<ColumnIx>) -> ZoneMove<'_> {
1128 let order = self.zone_order(zone);
1129 let positions: Vec<usize> = members
1130 .iter()
1131 .filter_map(|member| order.iter().position(|ix| ix == member))
1132 .collect();
1133 let span = match (positions.iter().min(), positions.iter().max()) {
1134 (Some(&first), Some(&last)) => first..last + 1,
1135 _ => 0..0,
1136 };
1137 let visible_at = order
1138 .iter()
1139 .enumerate()
1140 .filter(|(_, ix)| self.visible.contains(**ix))
1141 .map(|(position, _)| position)
1142 .collect();
1143 ZoneMove {
1144 context: self.move_context(zone),
1145 contiguous: span.len() == members.len(),
1146 members,
1147 span,
1148 visible_at,
1149 }
1150 }
1151
1152 pub(crate) fn band_segment(
1156 &self,
1157 zone: ColumnZone,
1158 band: &'static str,
1159 first: ColumnIx,
1160 ) -> Vec<ColumnIx> {
1161 let order = self.zone_order(zone);
1162 let is_member = |ix: &ColumnIx| self.bands_of(*ix).contains(&band);
1163 let Some(position) = order.iter().position(|ix| *ix == first) else {
1164 return Vec::new();
1165 };
1166 if !is_member(&first) {
1167 return Vec::new();
1168 }
1169 let start = order[..position]
1170 .iter()
1171 .rposition(|ix| !is_member(ix))
1172 .map_or(0, |p| p + 1);
1173 let end = order[position..]
1174 .iter()
1175 .position(|ix| !is_member(ix))
1176 .map_or(order.len(), |p| position + p);
1177 order[start..end].to_vec()
1178 }
1179
1180 pub(crate) fn band_members(&self, zone: ColumnZone, band: &'static str) -> Vec<ColumnIx> {
1182 self.zone_order(zone)
1183 .iter()
1184 .copied()
1185 .filter(|ix| self.bands_of(*ix).contains(&band))
1186 .collect()
1187 }
1188
1189 pub(crate) fn set_width(&mut self, ix: ColumnIx, width: Pixels) -> bool {
1191 let Some(column) = self.get(ix) else {
1192 return false;
1193 };
1194 let width = column.clamp_width(width);
1195 if self.widths[ix.get()] == width {
1196 return false;
1197 }
1198 self.widths[ix.get()] = width;
1199 self.settle();
1200 true
1201 }
1202
1203 pub(crate) fn set_visible(&mut self, ix: ColumnIx, visible: bool) -> Option<bool> {
1207 self.get(ix)?;
1208 if visible && self.internal.contains(ix) {
1209 return None;
1210 }
1211 if self.visible.contains(ix) == visible {
1212 return Some(false);
1213 }
1214 if visible {
1215 self.visible.insert(ix);
1216 } else {
1217 if self.visible.len() == 1 {
1218 return None;
1219 }
1220 let visible = self.visible;
1221 self.visible = column_set(
1222 self.order
1223 .iter()
1224 .copied()
1225 .filter(|c| *c != ix && visible.contains(*c)),
1226 );
1227 }
1228 self.settle();
1229 Some(true)
1230 }
1231
1232 pub(crate) fn show_all(&mut self) -> ColumnSet {
1235 let mut shown = ColumnSet::EMPTY;
1236 for &ix in &self.order {
1237 if !self.visible.contains(ix) && !self.internal.contains(ix) {
1238 shown.insert(ix);
1239 self.visible.insert(ix);
1240 }
1241 }
1242 if !shown.is_empty() {
1243 self.settle();
1244 }
1245 shown
1246 }
1247
1248 pub(crate) fn set_zone(
1253 &mut self,
1254 ix: ColumnIx,
1255 zone: ColumnZone,
1256 policy: &dyn ColumnMovePolicy,
1257 ) -> Vec<ColumnIx> {
1258 if self.get(ix).is_none() {
1259 return Vec::new();
1260 }
1261 let current = self.zone_of(ix);
1262 if current == zone {
1263 return Vec::new();
1264 }
1265 let order = self.zone_order(current);
1266 let position = order
1267 .iter()
1268 .position(|c| *c == ix)
1269 .expect("a schema column is in the order");
1270 let block = policy.zone_block(&self.move_context(current), ix);
1271 let block = if block.start <= position && position < block.end && block.end <= order.len() {
1272 block
1273 } else {
1274 position..position + 1
1275 };
1276 let members = order[block].to_vec();
1277 self.order.retain(|c| !members.contains(c));
1278 for member in &members {
1279 self.zones[member.get()] = zone;
1280 }
1281 let zones = &self.zones;
1282 let at = if zone.slot() < current.slot() {
1283 self.order
1284 .partition_point(|c| zones[c.get()].slot() <= zone.slot())
1285 } else {
1286 self.order
1287 .partition_point(|c| zones[c.get()].slot() < zone.slot())
1288 };
1289 self.order.splice(at..at, members.iter().copied());
1290 self.settle();
1291 members
1292 }
1293
1294 pub(crate) fn move_members(
1298 &mut self,
1299 zone: ColumnZone,
1300 members: &[ColumnIx],
1301 gap: usize,
1302 ) -> bool {
1303 let range = self.zone_range(zone);
1304 let order = &self.order[range.clone()];
1305 let gap = gap.min(order.len());
1306 let before = order[..gap].iter().filter(|c| members.contains(c)).count();
1307 let mut moved: Vec<ColumnIx> = order
1308 .iter()
1309 .copied()
1310 .filter(|c| !members.contains(c))
1311 .collect();
1312 let at = gap - before;
1313 moved.splice(at..at, members.iter().copied());
1314 if moved == order {
1315 return false;
1316 }
1317 self.order.splice(range, moved);
1318 self.settle();
1319 true
1320 }
1321
1322 pub(crate) fn move_before(
1325 &mut self,
1326 zone: ColumnZone,
1327 members: Vec<ColumnIx>,
1328 before: Option<ColumnIx>,
1329 policy: &dyn ColumnMovePolicy,
1330 ) -> Option<bool> {
1331 if members.is_empty() || members.iter().any(|ix| self.zone_of(*ix) != zone) {
1332 return None;
1333 }
1334 let order = self.zone_order(zone);
1335 let gap = match before {
1336 Some(before) => order.iter().position(|c| *c == before)?,
1337 None => order.len(),
1338 };
1339 let shift = self.zone_move(zone, members);
1340 if shift.contiguous && shift.span.start <= gap && gap <= shift.span.end {
1341 return Some(false);
1342 }
1343 if !policy.is_allowed(&shift.context, shift.span.clone(), gap) {
1344 return None;
1345 }
1346 let members = shift.members;
1347 Some(self.move_members(zone, &members, gap))
1348 }
1349}
1350
1351#[derive(Clone, Copy, Debug, Default)]
1353pub(super) struct LayoutDelta {
1354 pub(super) parts: LayoutParts,
1355 pub(super) columns: ColumnSet,
1356 pub(super) reveal: ColumnSet,
1358}
1359
1360impl LayoutDelta {
1361 pub(super) fn new(part: LayoutPart, columns: impl IntoIterator<Item = ColumnIx>) -> Self {
1362 let columns = column_set(columns);
1363 Self {
1364 parts: part.into(),
1365 columns,
1366 reveal: ColumnSet::EMPTY,
1367 }
1368 }
1369
1370 pub(super) fn revealing(mut self) -> Self {
1371 self.reveal = self.columns;
1372 self
1373 }
1374
1375 pub(super) fn is_empty(&self) -> bool {
1376 self.parts.is_empty()
1377 }
1378
1379 pub(super) fn union_with(&mut self, other: Self) {
1380 self.parts.union_with(other.parts);
1381 self.columns.union_with(other.columns);
1382 self.reveal.union_with(other.reveal);
1383 }
1384}
1385
1386fn column_set(columns: impl IntoIterator<Item = ColumnIx>) -> ColumnSet {
1387 columns.into_iter().fold(ColumnSet::EMPTY, |mut set, ix| {
1388 set.insert(ix);
1389 set
1390 })
1391}
1392
1393pub(super) fn column_of<C: GridColumns>(ix: ColumnIx) -> C {
1394 C::all()[ix.get()]
1395}
1396
1397impl<D: LiveGridDelegate> LiveGridState<D> {
1398 pub fn with_column_move_policy(mut self, policy: impl ColumnMovePolicy) -> Self {
1404 self.column_move = Box::new(policy);
1405 self.columns.reset(&*self.column_move);
1406 self.apply_initial_layout()
1408 }
1409
1410 pub fn with_autofit_policy(mut self, policy: impl AutofitPolicy) -> Self {
1414 self.autofit = Box::new(policy);
1415 self
1416 }
1417
1418 pub fn columns(&self) -> impl Iterator<Item = ColumnOf<D>> + '_ {
1421 self.columns.order().iter().map(|ix| column_of(*ix))
1422 }
1423
1424 pub fn visible_columns(&self) -> impl Iterator<Item = ColumnOf<D>> + '_ {
1426 self.columns.visible().map(column_of)
1427 }
1428
1429 pub fn column_width(&self, column: ColumnOf<D>) -> Pixels {
1431 self.columns.width(column.ix())
1432 }
1433
1434 pub fn is_column_visible(&self, column: ColumnOf<D>) -> bool {
1436 self.columns.is_visible(column.ix())
1437 }
1438
1439 pub fn column_zone(&self, column: ColumnOf<D>) -> ColumnZone {
1441 self.columns.zone_of(column.ix())
1442 }
1443
1444 pub fn view_column_ix(&self, column: ColumnOf<D>) -> Option<ViewColumnIx> {
1446 self.columns.view_column_ix(column.ix())
1447 }
1448
1449 pub fn column_at(&self, ix: ViewColumnIx) -> Option<ColumnOf<D>> {
1451 self.columns.column_at(ix).map(column_of)
1452 }
1453
1454 pub fn current_column(&self) -> Option<ColumnOf<D>> {
1458 self.current_column
1459 .filter(|ix| self.columns.is_visible(*ix))
1460 .map(column_of)
1461 }
1462
1463 pub(super) fn set_current_column(&mut self, column: ColumnIx) {
1466 self.current_column = Some(column);
1467 }
1468
1469 pub fn set_column_width(&mut self, column: ColumnOf<D>, width: Pixels, cx: &mut Context<Self>) {
1472 self.change_layout(LayoutOrigin::Programmatic, cx, |layout, _| {
1473 if layout.set_width(column.ix(), width) {
1474 LayoutDelta::new(LayoutPart::Width, [column.ix()])
1475 } else {
1476 LayoutDelta::default()
1477 }
1478 });
1479 }
1480
1481 pub fn set_column_visible(
1484 &mut self,
1485 column: ColumnOf<D>,
1486 visible: bool,
1487 cx: &mut Context<Self>,
1488 ) -> bool {
1489 self.set_column_visible_from(column.ix(), visible, LayoutOrigin::Programmatic, cx)
1490 }
1491
1492 fn set_column_visible_from(
1493 &mut self,
1494 ix: ColumnIx,
1495 visible: bool,
1496 origin: LayoutOrigin,
1497 cx: &mut Context<Self>,
1498 ) -> bool {
1499 let mut refused = false;
1500 self.change_layout(origin, cx, |layout, _| {
1501 match layout.set_visible(ix, visible) {
1502 Some(true) => LayoutDelta::new(LayoutPart::Visibility, [ix]),
1503 Some(false) => LayoutDelta::default(),
1504 None => {
1505 refused = true;
1506 LayoutDelta::default()
1507 }
1508 }
1509 });
1510 !refused
1511 }
1512
1513 pub fn show_all_columns(&mut self, cx: &mut Context<Self>) {
1515 self.show_all_columns_from(LayoutOrigin::Programmatic, cx);
1516 }
1517
1518 pub(super) fn show_all_columns_from(&mut self, origin: LayoutOrigin, cx: &mut Context<Self>) {
1519 self.change_layout(origin, cx, |layout, _| {
1520 let shown = layout.show_all();
1521 if shown.is_empty() {
1522 LayoutDelta::default()
1523 } else {
1524 LayoutDelta {
1525 parts: LayoutPart::Visibility.into(),
1526 columns: shown,
1527 reveal: ColumnSet::EMPTY,
1528 }
1529 }
1530 });
1531 }
1532
1533 pub fn set_column_zone(
1537 &mut self,
1538 column: ColumnOf<D>,
1539 zone: ColumnZone,
1540 cx: &mut Context<Self>,
1541 ) {
1542 self.set_column_zone_from(column.ix(), zone, LayoutOrigin::Programmatic, cx);
1543 }
1544
1545 fn set_column_zone_from(
1546 &mut self,
1547 ix: ColumnIx,
1548 zone: ColumnZone,
1549 origin: LayoutOrigin,
1550 cx: &mut Context<Self>,
1551 ) {
1552 self.change_layout(origin, cx, |layout, policy| {
1553 let moved = layout.set_zone(ix, zone, policy);
1554 if moved.is_empty() {
1555 return LayoutDelta::default();
1556 }
1557 let mut delta = LayoutDelta::new(LayoutPart::Zone, moved);
1558 delta.parts = delta.parts.with(LayoutPart::Order);
1559 delta.revealing()
1560 });
1561 }
1562
1563 pub fn move_column(
1566 &mut self,
1567 column: ColumnOf<D>,
1568 before: Option<ColumnOf<D>>,
1569 cx: &mut Context<Self>,
1570 ) -> bool {
1571 let ix = column.ix();
1572 if self.columns.get(ix).is_none() {
1573 return false;
1574 }
1575 let zone = self.columns.zone_of(ix);
1576 self.move_before(zone, vec![ix], before.map(|c| c.ix()), cx)
1577 }
1578
1579 pub fn move_band(
1583 &mut self,
1584 band: &'static str,
1585 before: Option<ColumnOf<D>>,
1586 cx: &mut Context<Self>,
1587 ) -> bool {
1588 let Some(&first) = self
1589 .columns
1590 .order()
1591 .iter()
1592 .find(|ix| self.columns.bands_of(**ix).contains(&band))
1593 else {
1594 return false;
1595 };
1596 let zone = self.columns.zone_of(first);
1597 let members = self.columns.band_members(zone, band);
1598 self.move_before(zone, members, before.map(|c| c.ix()), cx)
1599 }
1600
1601 fn move_before(
1602 &mut self,
1603 zone: ColumnZone,
1604 members: Vec<ColumnIx>,
1605 before: Option<ColumnIx>,
1606 cx: &mut Context<Self>,
1607 ) -> bool {
1608 let mut refused = false;
1609 self.change_layout(LayoutOrigin::Programmatic, cx, |layout, policy| {
1610 let moved = members.clone();
1611 match layout.move_before(zone, members, before, policy) {
1612 Some(true) => LayoutDelta::new(LayoutPart::Order, moved).revealing(),
1613 Some(false) => LayoutDelta::default(),
1614 None => {
1615 refused = true;
1616 LayoutDelta::default()
1617 }
1618 }
1619 });
1620 !refused
1621 }
1622
1623 pub fn autofit_column(&mut self, column: ColumnOf<D>, cx: &mut Context<Self>) {
1626 self.request_autofit(column_set([column.ix()]), LayoutOrigin::Programmatic, cx);
1627 }
1628
1629 pub fn autofit_columns(&mut self, cx: &mut Context<Self>) {
1631 self.request_autofit(self.columns.visible_set(), LayoutOrigin::Programmatic, cx);
1632 }
1633
1634 pub(super) fn request_autofit(
1635 &mut self,
1636 columns: ColumnSet,
1637 origin: LayoutOrigin,
1638 cx: &mut Context<Self>,
1639 ) {
1640 let pending = self
1641 .pending_autofit
1642 .get_or_insert((ColumnSet::EMPTY, origin));
1643 pending.0.union_with(columns);
1644 pending.1 = origin;
1645 cx.notify();
1646 }
1647
1648 pub(super) fn apply_pending_autofit(
1655 &mut self,
1656 needed_lines: usize,
1657 window: &Window,
1658 cx: &App,
1659 ) -> Option<(LayoutDelta, LayoutOrigin)> {
1660 let loading = self.projection.is_empty()
1661 && !self.snapshot.is_empty()
1662 && (self.rebuild_task.is_some() || self.pending != super::PendingWork::None);
1663 if loading {
1664 return None;
1665 }
1666 let (requested, origin) = self.pending_autofit.take()?;
1667 let projection = self.projection.clone();
1668 let snapshot = self.snapshot.clone();
1669 let start = self.scroll_row.min(projection.len());
1670 let mut end = start;
1671 let mut lines = 0;
1672 let mut summaries = Vec::new();
1674 while lines < needed_lines && end < projection.len() {
1675 match projection.line(end) {
1676 Some(DisplayRow::Data { row, .. }) => {
1677 lines += usize::from(snapshot.row(row).is_some());
1678 }
1679 Some(
1680 line @ (DisplayRow::GroupCaption { group, .. }
1681 | DisplayRow::GroupFooter { group, .. }),
1682 ) => {
1683 lines += 1;
1684 if let Some(group) = projection.groups().get(group) {
1685 summaries.push(SummaryLine {
1686 line: if matches!(line, DisplayRow::GroupFooter { .. }) {
1687 AggregateLine::Footer(group)
1688 } else {
1689 AggregateLine::Caption(group)
1690 },
1691 aggregates: group.aggregates(),
1692 weight: None,
1693 label: None,
1694 });
1695 }
1696 }
1697 None => {}
1698 }
1699 end += 1;
1700 }
1701
1702 let bands = self.body_bands();
1705 let mut pinned: Vec<Arc<D::Row>> = Vec::new();
1706 for edge in EDGES {
1707 for (line, _) in bands.visible_lines(edge) {
1708 match line {
1709 BodyLine::Pinned(_, ix) => {
1710 pinned.extend(snapshot.pinned_rows(edge).get(ix).cloned());
1711 }
1712 BodyLine::Totals(_) => summaries.push(SummaryLine {
1713 line: AggregateLine::Totals(edge),
1714 aggregates: projection.totals(),
1715 weight: Some(TOTALS_WEIGHT),
1716 label: self
1717 .columns
1718 .visible()
1719 .next()
1720 .map(|first| (self.summary_texts.total(), first)),
1721 }),
1722 BodyLine::Data(_) => {}
1723 }
1724 }
1725 }
1726
1727 let (font, font_size) = header_label_style(window);
1728 let mut widths = Vec::new();
1729 let mut buffer = String::new();
1730 let mut arena = Vec::new();
1731 let icon_width = cell_icon_size(self.row_height);
1734 let image_width = cell_image_size(self.row_height);
1735 let tree_column = projection.is_tree().then(|| self.tree_column()).flatten();
1736 let targets: Vec<ColumnIx> = self
1737 .columns
1738 .visible()
1739 .filter(|ix| requested.contains(*ix))
1740 .collect();
1741 for ix in targets {
1742 let column: ColumnOf<D> = column_of(ix);
1743 let schema_column = self.columns.column(ix);
1744 let title = schema_column.title.clone();
1745 let lane = if schema_column.sortable {
1748 HEADER_SORT_LANE
1749 } else {
1750 px(0.)
1751 };
1752 let icon = header_icon_lane(&schema_column.header_label, self.row_height);
1755 let title_width = match schema_column.header_label {
1756 HeaderLabel::Icon(_) => px(0.),
1757 HeaderLabel::Title | HeaderLabel::IconAndTitle(_) => self
1758 .header
1759 .labels
1760 .width(ix, &title, &font, font_size, window),
1761 };
1762 let header = icon + title_width + lane;
1763 let summary_width = SummaryMeasure {
1764 delegate: &self.delegate,
1765 app: cx,
1766 text_cache: &mut self.text_cache,
1767 buffer: &mut buffer,
1768 arena: &mut arena,
1769 format: self.columns.number_format(ix),
1770 }
1771 .width(&summaries, column, window);
1772 let text_cache = &mut self.text_cache;
1773 let format = self.columns.number_format(ix);
1774 let mut measure_text = |row: &D::Row| {
1775 let value = row.value(column);
1776 match value {
1777 CellValue::Icon(_) => return icon_width,
1778 CellValue::Image(_) => return image_width,
1779 _ => {}
1780 }
1781 let text = display_text_in(value, format, &mut buffer);
1782 if text.is_empty() {
1783 return px(0.);
1784 }
1785 arena.clear();
1786 text_cache
1787 .regular()
1788 .layout(text, window, &mut arena)
1789 .width()
1790 };
1791 let pinned_width = pinned
1792 .iter()
1793 .map(|row| measure_text(row))
1794 .fold(px(0.), Pixels::max);
1795 let mut measure = |display_ix: usize| {
1796 let DisplayRow::Data { row, depth } = projection.line(display_ix)? else {
1797 return None;
1798 };
1799 let row = snapshot.row(row)?;
1800 let indent = if tree_column == Some(ix) {
1801 INDENT * f32::from(depth) + LEAD_ICON_LANE
1802 } else {
1803 px(0.)
1804 };
1805 Some(measure_text(row) + indent)
1806 };
1807 let mut context =
1808 AutofitContext::new(header, start..end, projection.len(), &mut measure)
1809 .with_pinned_width(pinned_width)
1810 .with_summary_width(summary_width);
1811 let content = self.autofit.content_width(&mut context);
1812 widths.push((ix, content.ceil() + CELL_PADDING * 2. + px(1.)));
1813 }
1814 let delta = self.relayout(|layout, _| {
1815 let mut delta = LayoutDelta::default();
1816 for (ix, width) in widths {
1817 if layout.set_width(ix, width) {
1818 delta.union_with(LayoutDelta::new(LayoutPart::Width, [ix]));
1819 }
1820 }
1821 delta
1822 });
1823 (!delta.is_empty()).then_some((delta, origin))
1824 }
1825
1826 pub(super) fn change_layout(
1829 &mut self,
1830 origin: LayoutOrigin,
1831 cx: &mut Context<Self>,
1832 change: impl FnOnce(&mut ColumnLayout<ColumnOf<D>>, &dyn ColumnMovePolicy) -> LayoutDelta,
1833 ) -> bool {
1834 let visible = self.columns.visible_set();
1835 let delta = self.relayout(change);
1836 if delta.is_empty() {
1837 return false;
1838 }
1839 self.emit_layout_change(delta, origin, cx);
1840 if self.columns.visible_set() != visible {
1843 self.visible_columns_changed(cx);
1844 }
1845 cx.notify();
1846 true
1847 }
1848
1849 pub(super) fn relayout(
1853 &mut self,
1854 change: impl FnOnce(&mut ColumnLayout<ColumnOf<D>>, &dyn ColumnMovePolicy) -> LayoutDelta,
1855 ) -> LayoutDelta {
1856 let anchor = self.scroll_anchor();
1857 let delta = change(&mut self.columns, &*self.column_move);
1858 if delta.is_empty() {
1859 return delta;
1860 }
1861 self.restore_scroll_anchor(anchor);
1862 if !delta.reveal.is_empty() {
1863 self.reveal_columns(delta.reveal);
1864 }
1865 self.clamp_scroll();
1866 delta
1867 }
1868
1869 pub(super) fn emit_layout_change(
1872 &mut self,
1873 delta: LayoutDelta,
1874 origin: LayoutOrigin,
1875 cx: &mut Context<Self>,
1876 ) {
1877 cx.emit(super::LiveGridEvent::LayoutChange {
1878 parts: delta.parts,
1879 columns: delta.columns,
1880 origin,
1881 });
1882 let origin = match origin {
1883 LayoutOrigin::User => SelectionOrigin::User,
1884 LayoutOrigin::Programmatic => SelectionOrigin::Programmatic,
1885 };
1886 self.move_focus_off_hidden(origin, cx);
1887 }
1888
1889 pub(super) fn visible_columns_changed(&mut self, cx: &mut Context<Self>) {
1895 self.move_focus_off_hidden(SelectionOrigin::Programmatic, cx);
1896 self.quick_filter_columns_changed(cx);
1897 }
1898
1899 pub(crate) fn column_at_position(&self, position: Point<Pixels>) -> Option<ColumnIx> {
1901 let x = position.x - self.body_bounds.origin.x;
1902 self.columns
1903 .column_at_x(x, self.body_bounds.size.width, self.scroll_x)
1904 }
1905
1906 fn move_current_column(&mut self, step: isize, cx: &mut Context<Self>) {
1909 let Some(ix) = self
1910 .current_column
1911 .filter(|ix| self.columns.is_visible(*ix))
1912 else {
1913 return;
1914 };
1915 let Some((zone, i)) = self.columns.zone_position(ix) else {
1916 return;
1917 };
1918 let boundary = if step < 0 {
1919 match i.checked_sub(1) {
1920 Some(k) => k,
1921 None => return,
1922 }
1923 } else {
1924 i + 2
1925 };
1926 let Boundary::Gap(gap) = self
1927 .columns
1928 .zone_move(zone, vec![ix])
1929 .resolve(boundary, &*self.column_move)
1930 else {
1931 return;
1932 };
1933 self.change_layout(LayoutOrigin::User, cx, |layout, _| {
1934 if layout.move_members(zone, &[ix], gap) {
1935 LayoutDelta::new(LayoutPart::Order, [ix]).revealing()
1936 } else {
1937 LayoutDelta::default()
1938 }
1939 });
1940 }
1941
1942 fn current_visible_column(&self) -> Option<ColumnIx> {
1943 self.current_column
1944 .filter(|ix| self.columns.is_visible(*ix))
1945 }
1946
1947 pub(super) fn on_autofit_columns(
1948 &mut self,
1949 _: &AutofitColumns,
1950 _: &mut Window,
1951 cx: &mut Context<Self>,
1952 ) {
1953 self.request_autofit(self.columns.visible_set(), LayoutOrigin::User, cx);
1954 }
1955
1956 pub(super) fn on_autofit_column(
1957 &mut self,
1958 _: &AutofitColumn,
1959 _: &mut Window,
1960 cx: &mut Context<Self>,
1961 ) {
1962 if let Some(ix) = self.current_visible_column() {
1963 self.request_autofit(column_set([ix]), LayoutOrigin::User, cx);
1964 }
1965 }
1966
1967 pub(super) fn on_hide_column(
1968 &mut self,
1969 _: &HideColumn,
1970 _: &mut Window,
1971 cx: &mut Context<Self>,
1972 ) {
1973 if let Some(ix) = self.current_visible_column()
1976 && self.columns.column(ix).hideable
1977 {
1978 self.set_column_visible_from(ix, false, LayoutOrigin::User, cx);
1979 }
1980 }
1981
1982 pub(super) fn on_show_all_columns(
1983 &mut self,
1984 _: &ShowAllColumns,
1985 _: &mut Window,
1986 cx: &mut Context<Self>,
1987 ) {
1988 self.show_all_columns_from(LayoutOrigin::User, cx);
1989 }
1990
1991 pub(super) fn on_freeze_column_left(
1992 &mut self,
1993 _: &FreezeColumnLeft,
1994 _: &mut Window,
1995 cx: &mut Context<Self>,
1996 ) {
1997 if let Some(ix) = self.current_visible_column() {
1998 self.set_column_zone_from(ix, ColumnZone::FrozenLeft, LayoutOrigin::User, cx);
1999 }
2000 }
2001
2002 pub(super) fn on_freeze_column_right(
2003 &mut self,
2004 _: &FreezeColumnRight,
2005 _: &mut Window,
2006 cx: &mut Context<Self>,
2007 ) {
2008 if let Some(ix) = self.current_visible_column() {
2009 self.set_column_zone_from(ix, ColumnZone::FrozenRight, LayoutOrigin::User, cx);
2010 }
2011 }
2012
2013 pub(super) fn on_unfreeze_column(
2014 &mut self,
2015 _: &UnfreezeColumn,
2016 _: &mut Window,
2017 cx: &mut Context<Self>,
2018 ) {
2019 if let Some(ix) = self.current_visible_column() {
2020 self.set_column_zone_from(ix, ColumnZone::Scrolling, LayoutOrigin::User, cx);
2021 }
2022 }
2023
2024 pub(super) fn on_move_column_left(
2025 &mut self,
2026 _: &MoveColumnLeft,
2027 _: &mut Window,
2028 cx: &mut Context<Self>,
2029 ) {
2030 self.move_current_column(-1, cx);
2031 }
2032
2033 pub(super) fn on_move_column_right(
2034 &mut self,
2035 _: &MoveColumnRight,
2036 _: &mut Window,
2037 cx: &mut Context<Self>,
2038 ) {
2039 self.move_current_column(1, cx);
2040 }
2041}
2042
2043#[cfg(test)]
2044mod tests {
2045 use std::time::Duration;
2046
2047 use gpui_kit::px;
2048
2049 use super::{
2050 ColumnAlign, ColumnBand, ColumnLayout, ColumnZone, GridColumn, GridSchema,
2051 MIN_COLUMN_WIDTH, ViewColumnIx,
2052 };
2053 use crate::{
2054 highlight::HighlightPolicy,
2055 model::{ColumnIx, GridColumns},
2056 policy::{BandedMove, ColumnMovePolicy, FreeMove},
2057 state::GridTuning,
2058 store::StoreOptions,
2059 };
2060
2061 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2063 struct TestColumn(u16);
2064
2065 static ALL: [TestColumn; 12] = {
2066 let mut all = [TestColumn(0); 12];
2067 let mut ix = 0;
2068 while ix < 12 {
2069 all[ix] = TestColumn(ix as u16);
2070 ix += 1;
2071 }
2072 all
2073 };
2074
2075 const IDS: [&str; 12] = [
2076 "c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "c10", "c11",
2077 ];
2078 const BANDS: [&str; 12] = [
2079 "b0", "b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "b9", "b10", "b11",
2080 ];
2081
2082 impl GridColumns for TestColumn {
2083 fn all() -> &'static [Self] {
2084 &ALL
2085 }
2086
2087 fn id(self) -> &'static str {
2088 IDS[self.0 as usize]
2089 }
2090
2091 fn ix(self) -> ColumnIx {
2092 ColumnIx::new(self.0)
2093 }
2094 }
2095
2096 fn ix(i: u16) -> ColumnIx {
2097 ColumnIx::new(i)
2098 }
2099
2100 fn column(i: u16) -> GridColumn<TestColumn> {
2101 GridColumn::new(TestColumn(i), IDS[i as usize])
2102 }
2103
2104 fn zone_order(layout: &ColumnLayout<TestColumn>, zone: ColumnZone) -> Vec<u16> {
2105 layout
2106 .zone_order(zone)
2107 .iter()
2108 .map(|c| c.get() as u16)
2109 .collect()
2110 }
2111
2112 fn visible(layout: &ColumnLayout<TestColumn>) -> Vec<u16> {
2113 layout.visible().map(|c| c.get() as u16).collect()
2114 }
2115
2116 #[test]
2117 fn test_grid_schema_builder() {
2118 let bid = GridColumn::new(TestColumn(1), "Bid")
2119 .with_width(px(80.))
2120 .with_align(ColumnAlign::End)
2121 .with_min_width(px(40.))
2122 .with_max_width(px(300.))
2123 .with_zone(ColumnZone::FrozenRight)
2124 .with_visible(false)
2125 .with_resizable(false)
2126 .with_movable(false)
2127 .with_sortable(false)
2128 .with_groupable(false);
2129 assert_eq!(bid.column(), TestColumn(1));
2130 assert_eq!(bid.title().as_ref(), "Bid");
2131 assert_eq!(bid.width(), px(80.));
2132 assert_eq!(bid.align(), ColumnAlign::End);
2133 assert_eq!(bid.min_width(), px(40.));
2134 assert_eq!(bid.max_width(), px(300.));
2135 assert_eq!(bid.zone(), ColumnZone::FrozenRight);
2136 assert!(!bid.is_visible());
2137 assert!(!bid.is_resizable());
2138 assert!(!bid.is_movable());
2139 assert!(!bid.is_sortable());
2140 assert!(!bid.is_groupable());
2141 let plain = column(2);
2145 assert_eq!(plain.width(), px(100.));
2146 assert_eq!(plain.align(), ColumnAlign::Auto);
2147 assert_eq!(plain.min_width(), MIN_COLUMN_WIDTH);
2148 assert_eq!(plain.zone(), ColumnZone::Scrolling);
2149 assert!(plain.is_visible() && plain.is_resizable() && plain.is_movable());
2150 assert!(plain.is_sortable() && plain.is_groupable());
2151
2152 let band = ColumnBand::new("quote", "Quote")
2153 .column(column(2))
2154 .band(ColumnBand::new("depth", "Depth").column(column(3)));
2155 assert_eq!(band.id(), "quote");
2156 assert_eq!(band.title().as_ref(), "Quote");
2157
2158 let layout = ColumnLayout::new(
2160 GridSchema::new()
2161 .with_frozen(2)
2162 .column(column(0).with_zone(ColumnZone::FrozenRight))
2163 .band(band)
2164 .column(column(4).with_zone(ColumnZone::FrozenRight)),
2165 );
2166 assert_eq!(zone_order(&layout, ColumnZone::FrozenLeft), [0, 2]);
2167 assert_eq!(zone_order(&layout, ColumnZone::Scrolling), [3]);
2168 assert_eq!(zone_order(&layout, ColumnZone::FrozenRight), [4]);
2169 assert_eq!(layout.bands_of(ix(3)), ["quote", "depth"]);
2170 assert_eq!(layout.depth(), 2);
2171
2172 let tuning = GridTuning::default();
2173 assert_eq!(tuning.min_rebuild_interval, Duration::from_millis(100));
2174 assert_eq!(tuning.pull_interval, Duration::from_millis(8));
2175 let tuning = tuning
2176 .with_min_rebuild_interval(Duration::from_millis(250))
2177 .with_pull_interval(Duration::from_millis(16));
2178 assert_eq!(tuning.min_rebuild_interval, Duration::from_millis(250));
2179 assert_eq!(tuning.pull_interval, Duration::from_millis(16));
2180
2181 let options = StoreOptions::default();
2182 assert_eq!(options.highlight(), HighlightPolicy::default());
2183 assert_eq!(options.publish_interval(), Duration::from_millis(8));
2185 assert_eq!(
2186 options.publish_interval(),
2187 GridTuning::default().pull_interval
2188 );
2189 let highlight = HighlightPolicy::new(Duration::from_secs(2), 4);
2190 let options = options
2191 .with_highlight(highlight)
2192 .with_publish_interval(Duration::from_millis(10));
2193 assert_eq!(options.highlight(), highlight);
2194 assert_eq!(options.publish_interval(), Duration::from_millis(10));
2195 }
2196
2197 #[test]
2198 #[should_panic(expected = "already in the schema")]
2199 fn test_schema_rejects_repeated_column() {
2200 let _ = GridSchema::new()
2201 .column(column(1))
2202 .band(ColumnBand::new("b", "B").column(column(1)));
2203 }
2204
2205 #[test]
2206 #[should_panic(expected = "band \"b\" is already in the schema")]
2207 fn test_schema_rejects_repeated_band() {
2208 let _ = GridSchema::new()
2209 .band(ColumnBand::new("b", "B").column(column(1)))
2210 .band(ColumnBand::new("b", "B").column(column(2)));
2211 }
2212
2213 fn zoned() -> ColumnLayout<TestColumn> {
2216 ColumnLayout::new(
2217 GridSchema::new()
2218 .column(
2219 column(0)
2220 .with_width(px(50.))
2221 .with_zone(ColumnZone::FrozenLeft),
2222 )
2223 .column(column(1))
2224 .column(column(2).with_width(px(80.)))
2225 .column(column(3).with_width(px(120.)))
2226 .column(
2227 column(4)
2228 .with_width(px(60.))
2229 .with_zone(ColumnZone::FrozenRight),
2230 )
2231 .column(
2232 column(5)
2233 .with_width(px(40.))
2234 .with_zone(ColumnZone::FrozenRight),
2235 ),
2236 )
2237 }
2238
2239 #[test]
2240 fn test_column_layout_zones_and_offsets() {
2241 let layout = zoned();
2242 let offsets = |zone| layout.zone_columns(zone).offsets.clone();
2243 assert_eq!(offsets(ColumnZone::FrozenLeft), [px(0.), px(50.)]);
2244 assert_eq!(
2245 offsets(ColumnZone::Scrolling),
2246 [px(0.), px(100.), px(180.), px(300.)]
2247 );
2248 assert_eq!(
2249 offsets(ColumnZone::FrozenRight),
2250 [px(0.), px(60.), px(100.)]
2251 );
2252 assert_eq!(layout.total_width(), px(450.));
2253
2254 let geometry = layout.geometry(px(300.), px(0.));
2257 let [left, scrolling, right] = &geometry.zones;
2258 assert_eq!((left.x, left.width), (px(0.), px(50.)));
2259 assert_eq!((scrolling.x, scrolling.width), (px(50.), px(150.)));
2260 assert_eq!((right.x, right.width), (px(200.), px(100.)));
2261 assert_eq!(scrolling.columns, 0..2);
2262 assert_eq!(right.columns, 0..2);
2263 assert_eq!(layout.geometry(px(300.), px(100.)).zones[1].columns, 1..3);
2265 assert_eq!(layout.geometry(px(300.), px(160.)).zones[1].columns, 1..3);
2266
2267 for scroll_x in [px(0.), px(100.)] {
2269 assert_eq!(layout.column_at_x(px(10.), px(300.), scroll_x), Some(ix(0)));
2270 assert_eq!(
2271 layout.column_at_x(px(210.), px(300.), scroll_x),
2272 Some(ix(4))
2273 );
2274 assert_eq!(
2275 layout.column_at_x(px(270.), px(300.), scroll_x),
2276 Some(ix(5))
2277 );
2278 assert_eq!(layout.column_at_x(px(300.), px(300.), scroll_x), None);
2279 assert_eq!(layout.column_at_x(px(-1.), px(300.), scroll_x), None);
2280 }
2281 assert_eq!(layout.column_at_x(px(60.), px(300.), px(0.)), Some(ix(1)));
2282 assert_eq!(layout.column_at_x(px(60.), px(300.), px(100.)), Some(ix(2)));
2284 assert_eq!(layout.column_at_x(px(199.), px(300.), px(0.)), Some(ix(2)));
2285
2286 let geometry = layout.geometry(px(120.), px(0.));
2289 let [left, scrolling, right] = &geometry.zones;
2290 assert_eq!((left.x, left.width), (px(0.), px(50.)));
2291 assert_eq!((scrolling.width, scrolling.columns.clone()), (px(0.), 0..0));
2292 assert_eq!((right.x, right.width), (px(50.), px(70.)));
2293 assert_eq!(layout.column_at_x(px(60.), px(120.), px(0.)), Some(ix(4)));
2294 assert_eq!(layout.column_at_x(px(115.), px(120.), px(0.)), Some(ix(5)));
2295 let geometry = layout.geometry(px(30.), px(0.));
2296 assert_eq!(geometry.zones[0].width, px(30.));
2297 assert_eq!(geometry.zones[2].width, px(0.));
2298 assert_eq!(layout.column_at_x(px(29.), px(30.), px(0.)), Some(ix(0)));
2299
2300 let layout = ColumnLayout::new(
2302 GridSchema::new()
2303 .column(column(0).with_width(px(50.)))
2304 .column(column(1)),
2305 );
2306 let geometry = layout.geometry(px(120.), px(60.));
2307 assert_eq!(
2308 (geometry.zones[1].x, geometry.zones[1].width),
2309 (px(0.), px(120.))
2310 );
2311 assert_eq!(geometry.zones[1].columns, 1..2);
2312 assert_eq!(layout.column_at_x(px(10.), px(120.), px(60.)), Some(ix(1)));
2313
2314 let empty = ColumnLayout::new(GridSchema::<TestColumn>::new().with_frozen(2));
2315 assert_eq!(empty.total_width(), px(0.));
2316 assert_eq!(empty.geometry(px(200.), px(0.)).zones[1].columns, 0..0);
2317 assert_eq!(empty.column_at_x(px(0.), px(200.), px(0.)), None);
2318 }
2319
2320 #[test]
2321 fn test_zone_change_places_column_at_nearest_edge() {
2322 let layout = || {
2324 ColumnLayout::new(
2325 GridSchema::new()
2326 .with_frozen(2)
2327 .column(column(0))
2328 .column(column(1))
2329 .column(column(2))
2330 .column(column(3))
2331 .column(column(4))
2332 .column(column(5).with_zone(ColumnZone::FrozenRight))
2333 .column(column(6).with_zone(ColumnZone::FrozenRight)),
2334 )
2335 };
2336 let all = |layout: &ColumnLayout<TestColumn>| {
2337 ColumnZone::ALL.map(|zone| zone_order(layout, zone))
2338 };
2339 use ColumnZone::{FrozenLeft as L, FrozenRight as R, Scrolling as S};
2340 for (column, zone, expected) in [
2341 (3, L, [vec![0, 1, 3], vec![2, 4], vec![5, 6]]),
2343 (3, R, [vec![0, 1], vec![2, 4], vec![3, 5, 6]]),
2345 (0, S, [vec![1], vec![0, 2, 3, 4], vec![5, 6]]),
2347 (6, S, [vec![0, 1], vec![2, 3, 4, 6], vec![5]]),
2349 (0, R, [vec![1], vec![2, 3, 4], vec![0, 5, 6]]),
2351 (6, L, [vec![0, 1, 6], vec![2, 3, 4], vec![5]]),
2353 ] {
2354 let mut layout = layout();
2355 assert_eq!(layout.set_zone(ix(column), zone, &FreeMove), [ix(column)]);
2356 assert_eq!(all(&layout), expected, "c{column} to {zone:?}");
2357 assert_eq!(layout.zone_of(ix(column)), zone);
2358 assert!(layout.set_zone(ix(column), zone, &FreeMove).is_empty());
2360 }
2361 }
2362
2363 #[test]
2364 fn test_column_width_limits() {
2365 let narrow = column(0).with_min_width(px(5.));
2366 assert_eq!(
2367 narrow.min_width(),
2368 MIN_COLUMN_WIDTH,
2369 "raised to the minimum"
2370 );
2371 let limited = column(1)
2372 .with_width(px(2.))
2373 .with_min_width(px(30.))
2374 .with_max_width(px(200.));
2375 for (width, expected) in [
2376 (px(50.), px(50.)),
2377 (px(1000.), px(200.)),
2378 (px(12.), px(30.)),
2379 (px(0.), px(30.)),
2380 (px(-5.), px(30.)),
2381 (px(f32::NAN), px(30.)),
2382 (px(f32::INFINITY), px(30.)),
2383 ] {
2384 assert_eq!(limited.clamp_width(width), expected, "{width:?}");
2385 }
2386 assert_eq!(column(2).clamp_width(px(3.)), MIN_COLUMN_WIDTH);
2387
2388 let mut layout = ColumnLayout::new(GridSchema::new().column(limited).column(column(2)));
2389 assert_eq!(
2390 layout.width(ix(1)),
2391 px(30.),
2392 "the schema width is clamped too"
2393 );
2394 assert!(layout.set_width(ix(1), px(120.)));
2395 assert!(!layout.set_width(ix(1), px(120.)), "unchanged");
2396 assert!(layout.set_width(ix(2), px(f32::NAN)));
2397 assert_eq!(layout.width(ix(2)), MIN_COLUMN_WIDTH);
2398 assert!(!layout.set_width(ix(9), px(50.)), "not in the schema");
2399 assert_eq!(
2400 layout.zone_columns(ColumnZone::Scrolling).offsets[2],
2401 px(130.)
2402 );
2403 }
2404
2405 #[test]
2406 fn test_hide_show_keeps_position() {
2407 let mut layout =
2408 ColumnLayout::new((0..4).fold(GridSchema::new(), |schema, i| schema.column(column(i))));
2409 assert_eq!(layout.set_visible(ix(1), false), Some(true));
2410 assert_eq!(layout.set_visible(ix(1), false), Some(false));
2411 assert_eq!(visible(&layout), [0, 2, 3]);
2412 assert_eq!(zone_order(&layout, ColumnZone::Scrolling), [0, 1, 2, 3]);
2413 assert_eq!(
2415 layout.move_before(ColumnZone::Scrolling, vec![ix(3)], Some(ix(0)), &FreeMove),
2416 Some(true)
2417 );
2418 assert_eq!(zone_order(&layout, ColumnZone::Scrolling), [3, 0, 1, 2]);
2419 assert_eq!(layout.set_visible(ix(1), true), Some(true));
2420 assert_eq!(visible(&layout), [3, 0, 1, 2]);
2421
2422 for i in [0, 1, 2] {
2424 assert_eq!(layout.set_visible(ix(i), false), Some(true));
2425 }
2426 assert_eq!(layout.set_visible(ix(3), false), None);
2427 assert_eq!(visible(&layout), [3]);
2428 let shown = layout.show_all();
2429 assert_eq!(shown.len(), 3);
2430 assert!(shown.contains(ix(0)) && !shown.contains(ix(3)));
2431 assert_eq!(visible(&layout), [3, 0, 1, 2]);
2432 assert!(layout.show_all().is_empty());
2433 assert_eq!(layout.set_visible(ix(9), false), None, "not in the schema");
2434
2435 let layout = ColumnLayout::new(
2437 GridSchema::new()
2438 .column(column(5).with_visible(false))
2439 .column(column(6).with_visible(false)),
2440 );
2441 assert_eq!(visible(&layout), [5]);
2442 }
2443
2444 #[test]
2445 fn test_view_column_ix_round_trip() {
2446 let mut layout = zoned();
2447 layout.set_visible(ix(2), false);
2448 layout.set_visible(ix(5), false);
2449 assert_eq!(visible(&layout), [0, 1, 3, 4]);
2451 for (n, column) in layout.visible().collect::<Vec<_>>().into_iter().enumerate() {
2452 let view = layout.view_column_ix(column).expect("visible");
2453 assert_eq!(view, ViewColumnIx::new(n as u16));
2454 assert_eq!(view.get(), n);
2455 assert_eq!(layout.column_at(view), Some(column));
2456 }
2457 assert_eq!(layout.view_column_ix(ix(2)), None);
2458 assert_eq!(layout.view_column_ix(ix(5)), None);
2459 assert_eq!(layout.view_column_ix(ix(11)), None, "not in the schema");
2460 assert_eq!(layout.column_at(ViewColumnIx::new(4)), None);
2461 assert_eq!(
2462 layout.zone_position(ix(3)),
2463 Some((ColumnZone::Scrolling, 1))
2464 );
2465 assert_eq!(
2466 layout.zone_position(ix(4)),
2467 Some((ColumnZone::FrozenRight, 0))
2468 );
2469 }
2470
2471 fn banded() -> GridSchema<TestColumn> {
2473 GridSchema::new()
2474 .column(column(0))
2475 .band(
2476 ColumnBand::new("b0", "Outer")
2477 .column(column(1))
2478 .band(
2479 ColumnBand::new("b1", "Inner")
2480 .column(column(2))
2481 .column(column(3)),
2482 )
2483 .column(column(4)),
2484 )
2485 .column(column(5))
2486 }
2487
2488 #[test]
2489 fn test_banded_move_keeps_bands_together() {
2490 let mut layout = ColumnLayout::new(banded());
2491 layout.normalize(&BandedMove);
2492 let scrolling =
2493 |layout: &ColumnLayout<TestColumn>| zone_order(layout, ColumnZone::Scrolling);
2494 let zone = ColumnZone::Scrolling;
2495 assert_eq!(
2497 layout.move_before(zone, vec![ix(2)], Some(ix(0)), &BandedMove),
2498 None
2499 );
2500 assert_eq!(
2501 layout.move_before(zone, vec![ix(3)], Some(ix(2)), &BandedMove),
2502 Some(true)
2503 );
2504 assert_eq!(scrolling(&layout), [0, 1, 3, 2, 4, 5]);
2505 let members = layout.band_members(zone, "b0");
2507 assert_eq!(members, [1, 3, 2, 4].map(ix));
2508 assert_eq!(
2509 layout.move_before(zone, members, None, &BandedMove),
2510 Some(true)
2511 );
2512 assert_eq!(scrolling(&layout), [0, 5, 1, 3, 2, 4]);
2513 assert_eq!(
2515 layout.set_zone(ix(2), ColumnZone::FrozenLeft, &BandedMove),
2516 [1, 3, 2, 4].map(ix)
2517 );
2518 assert_eq!(zone_order(&layout, ColumnZone::FrozenLeft), [1, 3, 2, 4]);
2519 assert_eq!(
2521 layout.move_before(
2522 ColumnZone::FrozenLeft,
2523 vec![ix(1)],
2524 Some(ix(1)),
2525 &BandedMove
2526 ),
2527 Some(false)
2528 );
2529 let mut layout = ColumnLayout::new(banded());
2531 layout.set_zone(ix(4), ColumnZone::FrozenRight, &FreeMove);
2532 layout.move_before(zone, vec![ix(0)], Some(ix(3)), &FreeMove);
2533 assert_eq!(scrolling(&layout), [1, 2, 0, 3, 5]);
2534 layout.normalize(&BandedMove);
2535 assert_eq!(scrolling(&layout), [1, 2, 3, 4, 0, 5]);
2536 assert!(zone_order(&layout, ColumnZone::FrozenRight).is_empty());
2537 let before = layout.order().to_vec();
2539 layout.normalize(&BandedMove);
2540 assert_eq!(layout.order(), before);
2541 }
2542
2543 #[test]
2544 fn test_free_move_splits_band_runs() {
2545 let mut layout = ColumnLayout::new(banded());
2546 let zone = ColumnZone::Scrolling;
2547 assert_eq!(
2548 layout.move_before(zone, vec![ix(1)], None, &BandedMove),
2549 None
2550 );
2551 assert_eq!(
2552 layout.move_before(zone, vec![ix(1)], None, &FreeMove),
2553 Some(true)
2554 );
2555 assert_eq!(zone_order(&layout, zone), [0, 2, 3, 4, 5, 1]);
2556 let outer: Vec<_> = layout
2557 .runs()
2558 .iter()
2559 .filter(|run| run.band == "b0")
2560 .map(|run| (run.columns.clone(), run.title.clone()))
2561 .collect();
2562 assert_eq!(
2563 outer,
2564 [(1..4, "Outer".into()), (5..6, "Outer".into())],
2565 "two runs with one title"
2566 );
2567 assert_eq!(layout.band_segment(zone, "b0", ix(2)), [2, 3, 4].map(ix));
2569 assert_eq!(layout.band_segment(zone, "b0", ix(1)), [ix(1)]);
2570 assert!(layout.band_segment(zone, "b1", ix(1)).is_empty());
2571 }
2572
2573 #[test]
2574 fn test_band_runs_and_header_rows() {
2575 let mut layout = ColumnLayout::new(banded());
2576 assert_eq!(layout.depth(), 2);
2578 assert_eq!(layout.bands_of(ix(0)).len(), 0);
2580 assert_eq!(layout.bands_of(ix(1)).len(), 1);
2581 assert_eq!(layout.bands_of(ix(2)).len(), 2);
2582 let runs = |layout: &ColumnLayout<TestColumn>| {
2583 layout
2584 .runs()
2585 .iter()
2586 .map(|run| (run.band, run.level, run.columns.clone()))
2587 .collect::<Vec<_>>()
2588 };
2589 assert_eq!(runs(&layout), [("b0", 0, 1..5), ("b1", 1, 2..4)]);
2590 layout.set_visible(ix(2), false);
2593 layout.set_visible(ix(3), false);
2594 assert_eq!(runs(&layout), [("b0", 0, 1..3)]);
2595 layout.set_zone(ix(1), ColumnZone::FrozenRight, &BandedMove);
2597 assert_eq!(runs(&layout), [("b0", 0, 0..2)]);
2598 assert_eq!(layout.runs()[0].zone, ColumnZone::FrozenRight);
2599
2600 let flat = ColumnLayout::new(GridSchema::new().column(column(0)));
2601 assert_eq!(flat.depth(), 0);
2602 assert!(flat.runs().is_empty());
2603 }
2604
2605 fn random_schema(rng: &mut fastrand::Rng) -> (GridSchema<TestColumn>, usize) {
2608 fn items(
2609 rng: &mut fastrand::Rng,
2610 level: usize,
2611 next_column: &mut u16,
2612 next_band: &mut usize,
2613 limit: u16,
2614 ) -> Vec<Result<GridColumn<TestColumn>, ColumnBand<TestColumn>>> {
2615 let mut out = Vec::new();
2616 for _ in 0..rng.usize(1..=4) {
2617 if *next_column >= limit {
2618 break;
2619 }
2620 if level < 2 && *next_band < BANDS.len() && rng.u8(..3) == 0 {
2621 let id = BANDS[*next_band];
2622 *next_band += 1;
2623 let children = items(rng, level + 1, next_column, next_band, limit);
2624 let band = children
2625 .into_iter()
2626 .fold(ColumnBand::new(id, id), |band, child| match child {
2627 Ok(column) => band.column(column),
2628 Err(nested) => band.band(nested),
2629 });
2630 out.push(Err(band));
2631 } else {
2632 let i = *next_column;
2633 *next_column += 1;
2634 let zone = match rng.u8(..6) {
2635 0 => ColumnZone::FrozenLeft,
2636 1 => ColumnZone::FrozenRight,
2637 _ => ColumnZone::Scrolling,
2638 };
2639 out.push(Ok(column(i)
2640 .with_width(px(rng.u16(10..200) as f32))
2641 .with_zone(zone)
2642 .with_visible(rng.u8(..5) != 0)));
2643 }
2644 }
2645 out
2646 }
2647 let limit = rng.u16(1..=12);
2648 let (mut next_column, mut next_band) = (0, 0);
2649 let mut schema = GridSchema::new();
2650 while next_column < limit {
2651 for item in items(rng, 0, &mut next_column, &mut next_band, limit) {
2652 schema = match item {
2653 Ok(column) => schema.column(column),
2654 Err(band) => schema.band(band),
2655 };
2656 }
2657 }
2658 (schema, next_band)
2659 }
2660
2661 fn check_invariants(layout: &ColumnLayout<TestColumn>, banded: bool, bands: usize) {
2662 let order = layout.order();
2663 let mut seen = order.to_vec();
2664 seen.sort();
2665 seen.dedup();
2666 assert_eq!(seen.len(), order.len(), "no column twice: {order:?}");
2667 assert!(
2668 order.iter().all(|c| layout.get(*c).is_some()),
2669 "only schema columns"
2670 );
2671 assert!(
2672 order
2673 .windows(2)
2674 .all(|w| layout.zone_of(w[0]).slot() <= layout.zone_of(w[1]).slot()),
2675 "split by zone: {order:?}"
2676 );
2677 assert!(!layout.visible_set().is_empty(), "one column is visible");
2678 if banded {
2679 for band in &BANDS[..bands] {
2680 let positions: Vec<usize> = order
2681 .iter()
2682 .enumerate()
2683 .filter(|(_, c)| layout.bands_of(**c).contains(band))
2684 .map(|(p, _)| p)
2685 .collect();
2686 if let (Some(first), Some(last)) = (positions.first(), positions.last()) {
2687 assert_eq!(last - first + 1, positions.len(), "{band} is contiguous");
2688 let zone = layout.zone_of(order[*first]);
2689 assert!(positions.iter().all(|p| layout.zone_of(order[*p]) == zone));
2690 }
2691 }
2692 }
2693 for zone in ColumnZone::ALL {
2694 let columns = layout.zone_columns(zone);
2695 let mut x = px(0.);
2696 for (i, column) in columns.columns.iter().enumerate() {
2697 assert_eq!(columns.offsets[i], x);
2698 x += layout.width(*column);
2699 }
2700 assert_eq!(columns.content_width(), x);
2701 }
2702 let visible: Vec<ColumnIx> = layout.visible().collect();
2703 assert_eq!(visible.len(), layout.visible_set().len());
2704 for (n, column) in visible.iter().enumerate() {
2705 assert_eq!(
2706 layout.view_column_ix(*column),
2707 Some(ViewColumnIx::new(n as u16))
2708 );
2709 assert_eq!(layout.column_at(ViewColumnIx::new(n as u16)), Some(*column));
2710 }
2711 for column in order.iter().filter(|c| !layout.is_visible(**c)) {
2712 assert_eq!(layout.view_column_ix(*column), None);
2713 }
2714 }
2715
2716 #[test]
2717 fn test_column_ops_match_reference_model() {
2718 for seed in 0..60 {
2719 for banded in [true, false] {
2720 let mut rng = fastrand::Rng::with_seed(seed);
2721 let (schema, bands) = random_schema(&mut rng);
2722 let mut layout = ColumnLayout::new(schema);
2723 let policy: &dyn ColumnMovePolicy = if banded { &BandedMove } else { &FreeMove };
2724 layout.normalize(policy);
2725 let mut model: Vec<(ColumnIx, ColumnZone)> = layout
2727 .order()
2728 .iter()
2729 .map(|c| (*c, layout.zone_of(*c)))
2730 .collect();
2731 check_invariants(&layout, banded, bands);
2732 let columns: Vec<ColumnIx> = layout.order().to_vec();
2733 for step in 0..40 {
2734 let column = columns[rng.usize(..columns.len())];
2735 match rng.u8(..5) {
2736 0 => {
2737 let zone = layout.zone_of(column);
2738 let before = rng
2739 .bool()
2740 .then(|| columns[rng.usize(..columns.len())])
2741 .filter(|before| layout.zone_of(*before) == zone);
2742 let moved = layout.move_before(zone, vec![column], before, policy);
2743 if !banded {
2744 assert!(moved.is_some(), "free moves are never refused");
2745 let at = model.iter().position(|(c, _)| *c == column).unwrap();
2746 let entry = model.remove(at);
2747 let insert = match before {
2748 Some(before) if before != column => {
2749 model.iter().position(|(c, _)| *c == before).unwrap()
2750 }
2751 Some(_) => at,
2752 None => {
2753 model.iter().rposition(|(_, z)| *z == zone).map_or_else(
2754 || {
2755 model
2756 .iter()
2757 .filter(|(_, z)| z.slot() < zone.slot())
2758 .count()
2759 },
2760 |p| p + 1,
2761 )
2762 }
2763 };
2764 model.insert(insert, entry);
2765 }
2766 }
2767 1 => {
2768 let zone = ColumnZone::ALL[rng.usize(..3)];
2769 let moved = layout.set_zone(column, zone, policy);
2770 if !banded && !moved.is_empty() {
2771 let at = model.iter().position(|(c, _)| *c == column).unwrap();
2772 let (_, from) = model.remove(at);
2773 let insert = if zone.slot() < from.slot() {
2774 model
2775 .iter()
2776 .filter(|(_, z)| z.slot() <= zone.slot())
2777 .count()
2778 } else {
2779 model.iter().filter(|(_, z)| z.slot() < zone.slot()).count()
2780 };
2781 model.insert(insert, (column, zone));
2782 }
2783 }
2784 2 => {
2785 let shown = layout.is_visible(column);
2786 layout.set_visible(column, !shown);
2787 }
2788 3 => {
2789 layout.set_width(column, px(rng.u16(0..300) as f32));
2790 }
2791 _ => {
2792 if let Some(&band) = layout.bands_of(column).first() {
2793 let zone = layout.zone_of(column);
2794 let members = layout.band_members(zone, band);
2795 let before = columns[rng.usize(..columns.len())];
2796 let before = (layout.zone_of(before) == zone).then_some(before);
2797 let changed = layout.move_before(zone, members, before, policy);
2798 let top_level = before.is_none_or(|before| {
2799 layout.bands_of(before).is_empty()
2800 || layout.bands_of(before).first() == Some(&band)
2801 });
2802 if banded && top_level {
2803 assert!(changed.is_some(), "a whole band moves among items");
2804 } else if !banded && changed == Some(true) {
2805 model = layout
2806 .order()
2807 .iter()
2808 .map(|c| (*c, layout.zone_of(*c)))
2809 .collect();
2810 }
2811 }
2812 }
2813 }
2814 check_invariants(&layout, banded, bands);
2815 if !banded {
2816 let expected: Vec<ColumnIx> = model.iter().map(|(c, _)| *c).collect();
2817 assert_eq!(layout.order(), expected, "seed {seed} step {step}");
2818 }
2819 }
2820 }
2821 }
2822 }
2823}