1use gpui::Point;
6
7#[derive(Clone, Debug, PartialEq, Eq)]
11pub enum Selection {
12 None,
13 Cell(usize, usize),
14 Row(usize),
15 Column(usize),
16 Columns(Vec<usize>),
18 CellRange(usize, usize, usize, usize),
20 RowRange(usize, usize),
22 Rows(Vec<usize>),
24 Cells(Vec<(usize, usize)>),
26}
27
28impl Selection {
29 #[must_use]
32 pub fn normalized_bounds(&self) -> Option<(usize, usize, usize, usize)> {
33 match *self {
34 Selection::None => None,
35 Selection::Columns(ref cols) => {
36 let min = *cols.iter().min()?;
37 let max = *cols.iter().max()?;
38 Some((0, min, usize::MAX, max))
39 }
40 Selection::Cell(r, c) => Some((r, c, r, c)),
41 Selection::Row(r) => Some((r, 0, r, usize::MAX)),
42 Selection::Column(c) => Some((0, c, usize::MAX, c)),
43 Selection::CellRange(r1, c1, r2, c2) => {
44 Some((r1.min(r2), c1.min(c2), r1.max(r2), c1.max(c2)))
45 }
46 Selection::RowRange(r1, r2) => Some((r1.min(r2), 0, r1.max(r2), usize::MAX)),
47 Selection::Rows(ref rows) => {
48 let min = *rows.iter().min()?;
49 let max = *rows.iter().max()?;
50 Some((min, 0, max, usize::MAX))
51 }
52 Selection::Cells(ref cells) => {
53 let rmin = cells.iter().map(|&(r, _)| r).min()?;
54 let rmax = cells.iter().map(|&(r, _)| r).max()?;
55 let cmin = cells.iter().map(|&(_, c)| c).min()?;
56 let cmax = cells.iter().map(|&(_, c)| c).max()?;
57 Some((rmin, cmin, rmax, cmax))
58 }
59 }
60 }
61}
62
63#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
64pub enum SortDirection {
65 Ascending,
66 Descending,
67}
68
69#[derive(Clone, Copy, Debug, PartialEq, Eq)]
71pub enum HitResult {
72 None,
73 ColumnHeader(usize),
74 SortButton(usize),
75 ColumnBorder(usize),
76 GroupHeader(usize),
78 RowHeader(usize),
79 Cell(usize, usize),
80 Corner,
81 ContextMenuItem(usize),
82 VerticalScrollbar,
83 HorizontalScrollbar,
84}
85
86#[derive(Clone, Copy, Debug, PartialEq, Eq)]
87pub enum ScrollbarAxis {
88 Vertical,
89 Horizontal,
90}
91
92#[must_use]
94pub fn is_cell_selected(sel: &Selection, row: usize, col: usize) -> bool {
95 match *sel {
96 Selection::None => false,
97 Selection::Cell(r, c) => r == row && c == col,
98 Selection::CellRange(r1, c1, r2, c2) => {
99 let (rmin, cmin, rmax, cmax) = (r1.min(r2), c1.min(c2), r1.max(r2), c1.max(c2));
100 row >= rmin && row <= rmax && col >= cmin && col <= cmax
101 }
102 Selection::Row(r) => r == row,
103 Selection::RowRange(r1, r2) => {
104 let (rmin, rmax) = (r1.min(r2), r1.max(r2));
105 row >= rmin && row <= rmax
106 }
107 Selection::Column(c) => c == col,
108 Selection::Columns(ref cols) => cols.contains(&col),
109 Selection::Rows(ref rows) => rows.contains(&row),
110 Selection::Cells(ref cells) => cells.contains(&(row, col)),
111 }
112}
113
114#[must_use]
115pub fn is_row_selected(sel: &Selection, row: usize) -> bool {
116 match *sel {
117 Selection::Row(r) => r == row,
118 Selection::RowRange(r1, r2) => {
119 let (rmin, rmax) = (r1.min(r2), r1.max(r2));
120 row >= rmin && row <= rmax
121 }
122 Selection::Rows(ref rows) => rows.contains(&row),
123 _ => false,
124 }
125}
126
127#[must_use]
128pub fn is_column_selected(sel: &Selection, col: usize) -> bool {
129 match *sel {
130 Selection::Column(c) => c == col,
131 Selection::Columns(ref cols) => cols.contains(&col),
132 _ => false,
133 }
134}
135
136#[must_use]
139pub fn screen_to_content(
140 pos: Point<gpui::Pixels>,
141 bounds_origin: Point<gpui::Pixels>,
142 scroll: Point<gpui::Pixels>,
143) -> (f32, f32) {
144 let sx: f32 = scroll.x.into();
145 let sy: f32 = scroll.y.into();
146 let ox: f32 = bounds_origin.x.into();
147 let oy: f32 = bounds_origin.y.into();
148 let px: f32 = pos.x.into();
149 let py: f32 = pos.y.into();
150 (px - ox + sx, py - oy + sy)
151}
152
153#[must_use]
161pub fn to_grid_relative(
162 pos: Point<gpui::Pixels>,
163 bounds_origin: Point<gpui::Pixels>,
164) -> Point<gpui::Pixels> {
165 Point {
166 x: pos.x - bounds_origin.x,
167 y: pos.y - bounds_origin.y,
168 }
169}
170
171#[cfg(test)]
172#[allow(
173 clippy::unwrap_used,
174 clippy::expect_used,
175 clippy::field_reassign_with_default
176)]
177mod tests {
178 use super::*;
179 use gpui::{px, Pixels};
180
181 fn p(x: f32, y: f32) -> Point<Pixels> {
182 Point { x: px(x), y: px(y) }
183 }
184
185 #[test]
186 fn normalized_bounds_none_is_none() {
187 assert_eq!(Selection::None.normalized_bounds(), None);
188 }
189
190 #[test]
191 fn normalized_bounds_cell_folds_to_single_point() {
192 assert_eq!(
193 Selection::Cell(2, 3).normalized_bounds(),
194 Some((2, 3, 2, 3))
195 );
196 }
197
198 #[test]
199 fn normalized_bounds_row_spans_all_columns() {
200 let (r0, c0, r1, c1) = Selection::Row(4).normalized_bounds().unwrap();
201 assert_eq!(r0, 4);
202 assert_eq!(r1, 4);
203 assert_eq!(c0, 0);
204 assert_eq!(c1, usize::MAX);
205 }
206
207 #[test]
208 fn normalized_bounds_column_spans_all_rows() {
209 let (r0, c0, r1, c1) = Selection::Column(5).normalized_bounds().unwrap();
210 assert_eq!(r0, 0);
211 assert_eq!(r1, usize::MAX);
212 assert_eq!(c0, 5);
213 assert_eq!(c1, 5);
214 }
215
216 #[test]
217 fn normalized_bounds_cell_range_handles_reversed() {
218 assert_eq!(
219 Selection::CellRange(5, 4, 1, 2).normalized_bounds(),
220 Some((1, 2, 5, 4)),
221 );
222 }
223
224 #[test]
225 fn normalized_bounds_row_range_handles_reversed() {
226 let (r0, _c0, r1, c1) = Selection::RowRange(9, 3).normalized_bounds().unwrap();
227 assert_eq!(r0, 3);
228 assert_eq!(r1, 9);
229 assert_eq!(c1, usize::MAX);
230 }
231
232 #[test]
233 fn is_cell_selected_for_all_variants() {
234 assert!(!is_cell_selected(&Selection::None, 0, 0));
235 assert!(is_cell_selected(&Selection::Cell(2, 3), 2, 3));
236 assert!(!is_cell_selected(&Selection::Cell(2, 3), 3, 2));
237
238 assert!(is_cell_selected(&Selection::CellRange(1, 1, 3, 3), 2, 2));
239 assert!(is_cell_selected(&Selection::CellRange(3, 3, 1, 1), 2, 2));
240 assert!(!is_cell_selected(&Selection::CellRange(1, 1, 3, 3), 4, 4));
241
242 assert!(is_cell_selected(&Selection::Row(2), 2, 0));
243 assert!(is_cell_selected(&Selection::Row(2), 2, 99));
244 assert!(!is_cell_selected(&Selection::Row(2), 3, 0));
245
246 assert!(is_cell_selected(&Selection::RowRange(1, 3), 2, 5));
247 assert!(!is_cell_selected(&Selection::RowRange(1, 3), 4, 5));
248 assert!(is_cell_selected(&Selection::RowRange(3, 1), 2, 0));
249
250 assert!(is_cell_selected(&Selection::Column(5), 0, 5));
251 assert!(is_cell_selected(&Selection::Column(5), 99, 5));
252 assert!(!is_cell_selected(&Selection::Column(5), 0, 4));
253 }
254
255 #[test]
256 fn is_row_selected_only_for_row_and_row_range() {
257 assert!(is_row_selected(&Selection::Row(3), 3));
258 assert!(!is_row_selected(&Selection::Row(3), 4));
259 assert!(is_row_selected(&Selection::RowRange(2, 5), 4));
260 assert!(is_row_selected(&Selection::RowRange(5, 2), 4));
261 assert!(!is_row_selected(&Selection::RowRange(2, 5), 6));
262
263 assert!(!is_row_selected(&Selection::Cell(1, 2), 1));
264 assert!(!is_row_selected(&Selection::CellRange(0, 0, 9, 9), 5));
265 assert!(!is_row_selected(&Selection::Column(0), 5));
266 assert!(!is_row_selected(&Selection::None, 0));
267 }
268
269 #[test]
270 fn is_column_selected_only_for_column_variant() {
271 assert!(is_column_selected(&Selection::Column(7), 7));
272 assert!(!is_column_selected(&Selection::Column(7), 8));
273 assert!(!is_column_selected(&Selection::Row(0), 0));
274 assert!(!is_column_selected(&Selection::None, 0));
275 assert!(!is_column_selected(&Selection::CellRange(0, 2, 9, 2), 2));
276 }
277
278 #[test]
279 fn screen_to_content_applies_origin_and_scroll() {
280 let pos = p(50.0, 60.0);
281 let origin = p(10.0, 20.0);
282 let scroll = p(5.0, 7.0);
283 let (cx, cy) = screen_to_content(pos, origin, scroll);
284 assert_eq!(cx, 45.0);
285 assert_eq!(cy, 47.0);
286 }
287
288 #[test]
289 fn screen_to_content_no_offset() {
290 let (cx, cy) = screen_to_content(p(0.0, 0.0), p(0.0, 0.0), p(0.0, 0.0));
291 assert_eq!(cx, 0.0);
292 assert_eq!(cy, 0.0);
293 }
294
295 #[test]
296 fn screen_to_content_handles_negative_above_origin() {
297 let (_, _) = screen_to_content(p(-30.0, -30.0), p(0.0, 0.0), p(0.0, 0.0));
300 }
301}