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