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 RowHeader(usize),
77 Cell(usize, usize),
78 Corner,
79 ContextMenuItem(usize),
80 VerticalScrollbar,
81 HorizontalScrollbar,
82}
83
84#[derive(Clone, Copy, Debug, PartialEq, Eq)]
85pub enum ScrollbarAxis {
86 Vertical,
87 Horizontal,
88}
89
90#[must_use]
92pub fn is_cell_selected(sel: &Selection, row: usize, col: usize) -> bool {
93 match *sel {
94 Selection::None => false,
95 Selection::Cell(r, c) => r == row && c == col,
96 Selection::CellRange(r1, c1, r2, c2) => {
97 let (rmin, cmin, rmax, cmax) = (r1.min(r2), c1.min(c2), r1.max(r2), c1.max(c2));
98 row >= rmin && row <= rmax && col >= cmin && col <= cmax
99 }
100 Selection::Row(r) => r == row,
101 Selection::RowRange(r1, r2) => {
102 let (rmin, rmax) = (r1.min(r2), r1.max(r2));
103 row >= rmin && row <= rmax
104 }
105 Selection::Column(c) => c == col,
106 Selection::Columns(ref cols) => cols.contains(&col),
107 Selection::Rows(ref rows) => rows.contains(&row),
108 Selection::Cells(ref cells) => cells.contains(&(row, col)),
109 }
110}
111
112#[must_use]
113pub fn is_row_selected(sel: &Selection, row: usize) -> bool {
114 match *sel {
115 Selection::Row(r) => r == row,
116 Selection::RowRange(r1, r2) => {
117 let (rmin, rmax) = (r1.min(r2), r1.max(r2));
118 row >= rmin && row <= rmax
119 }
120 Selection::Rows(ref rows) => rows.contains(&row),
121 _ => false,
122 }
123}
124
125#[must_use]
126pub fn is_column_selected(sel: &Selection, col: usize) -> bool {
127 match *sel {
128 Selection::Column(c) => c == col,
129 Selection::Columns(ref cols) => cols.contains(&col),
130 _ => false,
131 }
132}
133
134#[must_use]
137pub fn screen_to_content(
138 pos: Point<gpui::Pixels>,
139 bounds_origin: Point<gpui::Pixels>,
140 scroll: Point<gpui::Pixels>,
141) -> (f32, f32) {
142 let sx: f32 = scroll.x.into();
143 let sy: f32 = scroll.y.into();
144 let ox: f32 = bounds_origin.x.into();
145 let oy: f32 = bounds_origin.y.into();
146 let px: f32 = pos.x.into();
147 let py: f32 = pos.y.into();
148 (px - ox + sx, py - oy + sy)
149}
150
151#[must_use]
159pub fn to_grid_relative(
160 pos: Point<gpui::Pixels>,
161 bounds_origin: Point<gpui::Pixels>,
162) -> Point<gpui::Pixels> {
163 Point {
164 x: pos.x - bounds_origin.x,
165 y: pos.y - bounds_origin.y,
166 }
167}
168
169#[cfg(test)]
170#[allow(
171 clippy::unwrap_used,
172 clippy::expect_used,
173 clippy::field_reassign_with_default
174)]
175mod tests {
176 use super::*;
177 use gpui::{px, Pixels};
178
179 fn p(x: f32, y: f32) -> Point<Pixels> {
180 Point { x: px(x), y: px(y) }
181 }
182
183 #[test]
184 fn normalized_bounds_none_is_none() {
185 assert_eq!(Selection::None.normalized_bounds(), None);
186 }
187
188 #[test]
189 fn normalized_bounds_cell_folds_to_single_point() {
190 assert_eq!(
191 Selection::Cell(2, 3).normalized_bounds(),
192 Some((2, 3, 2, 3))
193 );
194 }
195
196 #[test]
197 fn normalized_bounds_row_spans_all_columns() {
198 let (r0, c0, r1, c1) = Selection::Row(4).normalized_bounds().unwrap();
199 assert_eq!(r0, 4);
200 assert_eq!(r1, 4);
201 assert_eq!(c0, 0);
202 assert_eq!(c1, usize::MAX);
203 }
204
205 #[test]
206 fn normalized_bounds_column_spans_all_rows() {
207 let (r0, c0, r1, c1) = Selection::Column(5).normalized_bounds().unwrap();
208 assert_eq!(r0, 0);
209 assert_eq!(r1, usize::MAX);
210 assert_eq!(c0, 5);
211 assert_eq!(c1, 5);
212 }
213
214 #[test]
215 fn normalized_bounds_cell_range_handles_reversed() {
216 assert_eq!(
217 Selection::CellRange(5, 4, 1, 2).normalized_bounds(),
218 Some((1, 2, 5, 4)),
219 );
220 }
221
222 #[test]
223 fn normalized_bounds_row_range_handles_reversed() {
224 let (r0, _c0, r1, c1) = Selection::RowRange(9, 3).normalized_bounds().unwrap();
225 assert_eq!(r0, 3);
226 assert_eq!(r1, 9);
227 assert_eq!(c1, usize::MAX);
228 }
229
230 #[test]
231 fn is_cell_selected_for_all_variants() {
232 assert!(!is_cell_selected(&Selection::None, 0, 0));
233 assert!(is_cell_selected(&Selection::Cell(2, 3), 2, 3));
234 assert!(!is_cell_selected(&Selection::Cell(2, 3), 3, 2));
235
236 assert!(is_cell_selected(&Selection::CellRange(1, 1, 3, 3), 2, 2));
237 assert!(is_cell_selected(&Selection::CellRange(3, 3, 1, 1), 2, 2));
238 assert!(!is_cell_selected(&Selection::CellRange(1, 1, 3, 3), 4, 4));
239
240 assert!(is_cell_selected(&Selection::Row(2), 2, 0));
241 assert!(is_cell_selected(&Selection::Row(2), 2, 99));
242 assert!(!is_cell_selected(&Selection::Row(2), 3, 0));
243
244 assert!(is_cell_selected(&Selection::RowRange(1, 3), 2, 5));
245 assert!(!is_cell_selected(&Selection::RowRange(1, 3), 4, 5));
246 assert!(is_cell_selected(&Selection::RowRange(3, 1), 2, 0));
247
248 assert!(is_cell_selected(&Selection::Column(5), 0, 5));
249 assert!(is_cell_selected(&Selection::Column(5), 99, 5));
250 assert!(!is_cell_selected(&Selection::Column(5), 0, 4));
251 }
252
253 #[test]
254 fn is_row_selected_only_for_row_and_row_range() {
255 assert!(is_row_selected(&Selection::Row(3), 3));
256 assert!(!is_row_selected(&Selection::Row(3), 4));
257 assert!(is_row_selected(&Selection::RowRange(2, 5), 4));
258 assert!(is_row_selected(&Selection::RowRange(5, 2), 4));
259 assert!(!is_row_selected(&Selection::RowRange(2, 5), 6));
260
261 assert!(!is_row_selected(&Selection::Cell(1, 2), 1));
262 assert!(!is_row_selected(&Selection::CellRange(0, 0, 9, 9), 5));
263 assert!(!is_row_selected(&Selection::Column(0), 5));
264 assert!(!is_row_selected(&Selection::None, 0));
265 }
266
267 #[test]
268 fn is_column_selected_only_for_column_variant() {
269 assert!(is_column_selected(&Selection::Column(7), 7));
270 assert!(!is_column_selected(&Selection::Column(7), 8));
271 assert!(!is_column_selected(&Selection::Row(0), 0));
272 assert!(!is_column_selected(&Selection::None, 0));
273 assert!(!is_column_selected(&Selection::CellRange(0, 2, 9, 2), 2));
274 }
275
276 #[test]
277 fn screen_to_content_applies_origin_and_scroll() {
278 let pos = p(50.0, 60.0);
279 let origin = p(10.0, 20.0);
280 let scroll = p(5.0, 7.0);
281 let (cx, cy) = screen_to_content(pos, origin, scroll);
282 assert_eq!(cx, 45.0);
283 assert_eq!(cy, 47.0);
284 }
285
286 #[test]
287 fn screen_to_content_no_offset() {
288 let (cx, cy) = screen_to_content(p(0.0, 0.0), p(0.0, 0.0), p(0.0, 0.0));
289 assert_eq!(cx, 0.0);
290 assert_eq!(cy, 0.0);
291 }
292
293 #[test]
294 fn screen_to_content_handles_negative_above_origin() {
295 let (_, _) = screen_to_content(p(-30.0, -30.0), p(0.0, 0.0), p(0.0, 0.0));
298 }
299}