1use std::{hash::Hash, sync::Arc};
20
21use gpui_kit::{Bounds, Pixels, Point, point, px, size};
22
23use super::{KeyOf, LiveGridDelegate, LiveGridState, key_hash};
24use crate::{model::GridRow, store::PinnedEdge};
25
26pub(super) const EDGES: [PinnedEdge; 2] = [PinnedEdge::Top, PinnedEdge::Bottom];
28
29pub(super) const fn band_slot(edge: PinnedEdge) -> usize {
31 match edge {
32 PinnedEdge::Top => 0,
33 PinnedEdge::Bottom => 1,
34 }
35}
36
37pub(crate) const fn band_name(edge: PinnedEdge) -> &'static str {
40 match edge {
41 PinnedEdge::Top => "live-grid-pinned-top",
42 PinnedEdge::Bottom => "live-grid-pinned-bottom",
43 }
44}
45
46pub(crate) fn pinned_key_hash<K: Hash + ?Sized>(edge: PinnedEdge, key: &K) -> u64 {
53 key_hash(&(band_name(edge), key))
54}
55
56#[derive(Clone, Copy, Debug, Default, PartialEq)]
59pub(crate) struct BodyBands {
60 top: Bounds<Pixels>,
61 rows: Bounds<Pixels>,
62 bottom: Bounds<Pixels>,
63 row_height: Pixels,
64 pinned: [usize; 2],
66 totals: Option<PinnedEdge>,
68}
69
70impl BodyBands {
71 pub(super) fn new(
74 body: Bounds<Pixels>,
75 row_height: Pixels,
76 top: usize,
77 bottom: usize,
78 totals: Option<PinnedEdge>,
79 ) -> Self {
80 let mut bands = Self {
81 row_height: row_height.max(px(0.)),
82 pinned: [top, bottom],
83 totals,
84 ..Self::default()
85 };
86 let height = body.size.height.max(px(0.));
87 let line_height = bands.row_height;
88 let top_height = (line_height * bands.lines(PinnedEdge::Top) as f32).min(height);
89 let bottom_height =
90 (line_height * bands.lines(PinnedEdge::Bottom) as f32).min(height - top_height);
91 let (x, y, width) = (body.origin.x, body.origin.y, body.size.width);
92 bands.top = Bounds::new(point(x, y), size(width, top_height));
93 bands.rows = Bounds::new(
94 point(x, y + top_height),
95 size(width, height - top_height - bottom_height),
96 );
97 bands.bottom = Bounds::new(
98 point(x, y + height - bottom_height),
99 size(width, bottom_height),
100 );
101 bands
102 }
103
104 pub(crate) fn rows(&self) -> Bounds<Pixels> {
106 self.rows
107 }
108
109 pub(crate) fn band(&self, edge: PinnedEdge) -> Bounds<Pixels> {
111 match edge {
112 PinnedEdge::Top => self.top,
113 PinnedEdge::Bottom => self.bottom,
114 }
115 }
116
117 pub(super) fn lines(&self, edge: PinnedEdge) -> usize {
120 self.pinned[band_slot(edge)] + usize::from(self.totals == Some(edge))
121 }
122
123 pub(super) fn band_line(&self, edge: PinnedEdge, line: usize) -> BodyLine {
126 match (self.totals == Some(edge), edge) {
127 (false, _) => BodyLine::Pinned(edge, line),
128 (true, PinnedEdge::Top) => match line.checked_sub(1) {
129 Some(ix) => BodyLine::Pinned(edge, ix),
130 None => BodyLine::Totals(edge),
131 },
132 (true, PinnedEdge::Bottom) if line + 1 == self.lines(edge) => BodyLine::Totals(edge),
133 (true, PinnedEdge::Bottom) => BodyLine::Pinned(edge, line),
134 }
135 }
136
137 pub(super) fn line_y(&self, edge: PinnedEdge, line: usize) -> Option<Pixels> {
141 let band = self.band(edge);
142 let y = match edge {
143 PinnedEdge::Top => band.origin.y + self.row_height * line as f32,
144 PinnedEdge::Bottom => {
145 band.bottom() - self.row_height * (self.lines(edge).checked_sub(line)?) as f32
146 }
147 };
148 (y < band.bottom() && y + self.row_height > band.origin.y).then_some(y)
149 }
150
151 pub(super) fn visible_lines(
154 &self,
155 edge: PinnedEdge,
156 ) -> impl Iterator<Item = (BodyLine, Pixels)> + '_ {
157 (0..self.lines(edge))
158 .filter_map(move |line| Some((self.band_line(edge, line), self.line_y(edge, line)?)))
159 }
160
161 fn line_at(&self, edge: PinnedEdge, y: Pixels) -> Option<BodyLine> {
164 let band = self.band(edge);
165 if y < band.origin.y || y >= band.bottom() || self.row_height <= px(0.) {
166 return None;
167 }
168 let line = match edge {
169 PinnedEdge::Top => ((y - band.origin.y) / self.row_height).floor() as usize,
170 PinnedEdge::Bottom => {
171 let from_edge = ((band.bottom() - y) / self.row_height).ceil() as usize;
172 self.lines(edge).checked_sub(from_edge.max(1))?
173 }
174 };
175 (line < self.lines(edge)).then(|| self.band_line(edge, line))
176 }
177}
178
179#[derive(Clone, Copy, Debug, PartialEq, Eq)]
182pub(super) enum BodyLine {
183 Data(usize),
184 Pinned(PinnedEdge, usize),
185 Totals(PinnedEdge),
187}
188
189pub(super) struct PaintedBands<R> {
194 rows: [Vec<Arc<R>>; 2],
195 first: [usize; 2],
198}
199
200impl<R> Default for PaintedBands<R> {
201 fn default() -> Self {
202 Self {
203 rows: [Vec::new(), Vec::new()],
204 first: [0; 2],
205 }
206 }
207}
208
209impl<R> PaintedBands<R> {
210 pub(super) fn rows(&self, edge: PinnedEdge) -> &[Arc<R>] {
212 &self.rows[band_slot(edge)]
213 }
214
215 fn row(&self, edge: PinnedEdge, ix: usize) -> Option<&Arc<R>> {
217 let slot = band_slot(edge);
218 self.rows[slot].get(ix.checked_sub(self.first[slot])?)
219 }
220
221 pub(super) fn begin(&mut self, edge: PinnedEdge) {
223 let slot = band_slot(edge);
224 self.rows[slot].clear();
225 self.first[slot] = 0;
226 }
227
228 pub(super) fn push(&mut self, edge: PinnedEdge, ix: usize, row: Arc<R>) {
231 let slot = band_slot(edge);
232 if self.rows[slot].is_empty() {
233 self.first[slot] = ix;
234 }
235 debug_assert_eq!(self.first[slot] + self.rows[slot].len(), ix, "no gaps");
236 self.rows[slot].push(row);
237 }
238}
239
240impl<D: LiveGridDelegate> LiveGridState<D> {
241 pub fn pinned_keys(&self, edge: PinnedEdge) -> Vec<KeyOf<D>> {
246 self.painted_pinned
247 .rows(edge)
248 .iter()
249 .map(|row| row.key().clone())
250 .collect()
251 }
252
253 pub(crate) fn body_bands(&self) -> BodyBands {
256 BodyBands::new(
257 self.body_bounds,
258 self.row_height,
259 self.snapshot.pinned_rows(PinnedEdge::Top).len(),
260 self.snapshot.pinned_rows(PinnedEdge::Bottom).len(),
261 self.totals,
262 )
263 }
264
265 pub(super) fn rows_bounds(&self) -> Bounds<Pixels> {
267 self.body_bands().rows()
268 }
269
270 pub(super) fn body_line_at(&self, position: Point<Pixels>) -> Option<BodyLine> {
273 let bands = self.body_bands();
274 for edge in EDGES {
275 if bands.band(edge).contains(&position) {
276 return bands.line_at(edge, position.y);
277 }
278 }
279 self.line_at(position).map(BodyLine::Data)
280 }
281
282 pub(super) fn pinned_row_at(&self, position: Point<Pixels>) -> Option<(PinnedEdge, KeyOf<D>)> {
284 let Some(BodyLine::Pinned(edge, ix)) = self.body_line_at(position) else {
285 return None;
286 };
287 let row = self.painted_pinned.row(edge, ix)?;
288 Some((edge, row.key().clone()))
289 }
290
291 pub(super) fn is_pinned_key(&self, edge: PinnedEdge, key: &KeyOf<D>) -> bool {
294 self.snapshot
295 .pinned_rows(edge)
296 .iter()
297 .any(|row| row.key() == key)
298 }
299}
300
301#[cfg(test)]
302mod tests {
303 use gpui_kit::{Bounds, point, px, size};
304
305 use super::{BodyBands, BodyLine, band_slot, pinned_key_hash};
306 use crate::{state::key_hash, store::PinnedEdge};
307
308 #[test]
309 fn test_pinned_key_hash() {
310 for key in [0u32, 1, 42] {
311 let top = pinned_key_hash(PinnedEdge::Top, &key);
312 let bottom = pinned_key_hash(PinnedEdge::Bottom, &key);
313 assert_ne!(top, key_hash(&key), "{key}");
314 assert_ne!(bottom, key_hash(&key), "{key}");
315 assert_ne!(top, bottom, "{key}");
316 }
317 assert_eq!(
318 pinned_key_hash(PinnedEdge::Top, "total"),
319 pinned_key_hash(PinnedEdge::Top, &String::from("total"))
320 );
321 assert_eq!(key_hash(&(PinnedEdge::Top, 7u32)), key_hash(&7u32));
324 }
325
326 #[test]
327 fn test_body_bands_overflow_rule() {
328 let body = Bounds::new(point(px(10.), px(40.)), size(px(300.), px(260.)));
329 let row = px(26.);
330
331 let bands = BodyBands::new(body, row, 0, 0, None);
333 assert_eq!(bands.rows(), body);
334 assert_eq!(bands.band(PinnedEdge::Top).size.height, px(0.));
335 assert_eq!(bands.band(PinnedEdge::Bottom).size.height, px(0.));
336 assert_eq!(bands.band(PinnedEdge::Bottom).origin.y, px(300.));
337
338 let bands = BodyBands::new(body, row, 1, 1, None);
340 assert_eq!(
341 bands.band(PinnedEdge::Top),
342 Bounds::new(point(px(10.), px(40.)), size(px(300.), px(26.)))
343 );
344 assert_eq!(
345 bands.rows(),
346 Bounds::new(point(px(10.), px(66.)), size(px(300.), px(208.)))
347 );
348 assert_eq!(
349 bands.band(PinnedEdge::Bottom),
350 Bounds::new(point(px(10.), px(274.)), size(px(300.), px(26.)))
351 );
352
353 let bands = BodyBands::new(body, row, 6, 6, None);
356 assert_eq!(bands.band(PinnedEdge::Top).size.height, px(156.));
357 assert_eq!(bands.band(PinnedEdge::Bottom).size.height, px(104.));
358 assert_eq!(bands.rows().size.height, px(0.));
359 let bands = BodyBands::new(body, row, 20, 1, None);
360 assert_eq!(bands.band(PinnedEdge::Top).size.height, px(260.));
361 assert_eq!(bands.band(PinnedEdge::Bottom).size.height, px(0.));
362 assert_eq!(bands.rows().size.height, px(0.));
363
364 let bands = BodyBands::new(Bounds::default(), row, 2, 1, None);
366 assert_eq!(bands.rows().size.height, px(0.));
367 assert_eq!(bands.band(PinnedEdge::Top).size.height, px(0.));
368
369 assert_eq!(band_slot(PinnedEdge::Top), 0);
370 assert_eq!(band_slot(PinnedEdge::Bottom), 1);
371 }
372
373 #[test]
374 fn test_body_bands_with_totals() {
375 let body = Bounds::new(point(px(10.), px(40.)), size(px(300.), px(260.)));
376 let row = px(26.);
377 let lines = |bands: &BodyBands, edge| -> Vec<(BodyLine, f32)> {
378 bands
379 .visible_lines(edge)
380 .map(|(line, y)| (line, y.as_f32()))
381 .collect()
382 };
383 use BodyLine::{Pinned, Totals};
384 use PinnedEdge::{Bottom, Top};
385
386 let bands = BodyBands::new(body, row, 1, 1, Some(Bottom));
389 assert_eq!(bands.band(Bottom).size.height, px(52.));
390 assert_eq!(bands.rows().size.height, px(182.));
391 assert_eq!(
392 lines(&bands, Bottom),
393 [(Pinned(Bottom, 0), 248.), (Totals(Bottom), 274.)]
394 );
395 assert_eq!(lines(&bands, Top), [(Pinned(Top, 0), 40.)]);
396 let bands = BodyBands::new(body, row, 0, 0, Some(Bottom));
398 assert_eq!(lines(&bands, Bottom), [(Totals(Bottom), 274.)]);
399
400 let bands = BodyBands::new(body, row, 2, 0, Some(Top));
402 assert_eq!(bands.band(Top).size.height, px(78.));
403 assert_eq!(
404 lines(&bands, Top),
405 [
406 (Totals(Top), 40.),
407 (Pinned(Top, 0), 66.),
408 (Pinned(Top, 1), 92.)
409 ]
410 );
411 assert_eq!(bands.line_at(Top, px(40.)), Some(Totals(Top)));
412 assert_eq!(bands.line_at(Top, px(91.)), Some(Pinned(Top, 0)));
413 assert_eq!(bands.line_at(Top, px(118.)), None);
414
415 let bands = BodyBands::new(body, row, 8, 3, Some(Bottom));
419 assert_eq!(bands.band(Bottom).size.height, px(52.));
420 assert_eq!(
421 lines(&bands, Bottom),
422 [(Pinned(Bottom, 2), 248.), (Totals(Bottom), 274.)]
423 );
424 assert_eq!(bands.line_at(Bottom, px(299.)), Some(Totals(Bottom)));
425 assert_eq!(bands.line_at(Bottom, px(274.)), Some(Totals(Bottom)));
426 assert_eq!(bands.line_at(Bottom, px(273.)), Some(Pinned(Bottom, 2)));
427 assert_eq!(bands.line_at(Bottom, px(300.)), None);
428 let short = Bounds::new(body.origin, size(px(300.), px(26. * 3. + 13.)));
431 let bands = BodyBands::new(short, row, 2, 3, Some(Bottom));
432 assert_eq!(bands.band(Top).size.height, px(52.));
433 assert_eq!(bands.band(Bottom).size.height, px(39.));
434 assert_eq!(
435 lines(&bands, Bottom),
436 [(Pinned(Bottom, 2), 79.), (Totals(Bottom), 105.)]
437 );
438 assert_eq!(bands.line_at(Bottom, px(92.)), Some(Pinned(Bottom, 2)));
439 let bands = BodyBands::new(short, row, 5, 0, Some(Top));
442 assert_eq!(bands.rows().size.height, px(0.));
443 assert_eq!(
444 lines(&bands, Top),
445 [
446 (Totals(Top), 40.),
447 (Pinned(Top, 0), 66.),
448 (Pinned(Top, 1), 92.),
449 (Pinned(Top, 2), 118.)
450 ]
451 );
452 }
453}