teksilo_widgets/grid_view/layout/
uniform.rs1use teksilo_canvas::{EdgeInsets, Point};
12
13use super::columns::{ColumnGeometry, column_at, geometry_for};
14use super::strategy::{BUFFER_ROWS, GridLayoutStrategy, GridSizing, TileRect, VisibleTileRange};
15
16#[derive(Debug, Clone)]
18pub struct UniformGrid {
19 columns: ColumnGeometry,
20 tile_height: f32,
21 row_gap: f32,
22 inset: EdgeInsets,
23}
24
25impl UniformGrid {
26 pub(crate) fn new(sizing: GridSizing, col_gap: f32, row_gap: f32, inset: EdgeInsets) -> Self {
28 Self {
29 columns: geometry_for(sizing, col_gap, inset),
30 tile_height: sizing.tile_height().max(0.0),
31 row_gap: row_gap.max(0.0),
32 inset,
33 }
34 }
35
36 fn row_step(&self) -> f32 {
37 self.tile_height + self.row_gap
38 }
39
40 fn row_count(&self, item_count: usize, viewport_width: f32) -> usize {
41 if item_count == 0 {
42 return 0;
43 }
44 item_count.div_ceil(self.column_count(viewport_width).max(1))
45 }
46}
47
48impl GridLayoutStrategy for UniformGrid {
49 fn column_count(&self, viewport_width: f32) -> usize {
50 self.columns.column_count(viewport_width)
51 }
52
53 fn column_x(&self, col: usize, viewport_width: f32) -> (f32, f32) {
54 self.columns.column_x(col, viewport_width)
55 }
56
57 fn total_content_height(&self, item_count: usize, viewport_width: f32) -> f32 {
58 let rows = self.row_count(item_count, viewport_width);
59 if rows == 0 {
60 return 0.0;
61 }
62 self.inset.top + rows as f32 * self.row_step() - self.row_gap + self.inset.bottom
63 }
64
65 fn visible_range(
66 &self,
67 scroll_y: f32,
68 viewport_height: f32,
69 viewport_width: f32,
70 item_count: usize,
71 ) -> VisibleTileRange {
72 if item_count == 0 || self.row_step() <= 0.0 {
73 return VisibleTileRange { start: 0, end: 0 };
74 }
75 let cols = self.column_count(viewport_width).max(1);
76 let row_step = self.row_step();
77 let content_scroll = (scroll_y - self.inset.top).max(0.0);
78 let first_row = (content_scroll / row_step).floor() as usize;
79 let last_row = ((content_scroll + viewport_height) / row_step).ceil() as usize;
80 let start_row = first_row.saturating_sub(BUFFER_ROWS);
81 let end_row = last_row + BUFFER_ROWS;
82 let start = (start_row * cols).min(item_count);
83 let end = (end_row.saturating_add(1).saturating_mul(cols)).min(item_count);
84 VisibleTileRange { start, end }
85 }
86
87 fn tile_rect(&self, index: usize, viewport_width: f32) -> TileRect {
88 let cols = self.column_count(viewport_width).max(1);
89 let row = index / cols;
90 let col = index % cols;
91 let (x, width) = self.column_x(col, viewport_width);
92 let y = self.inset.top + row as f32 * self.row_step();
93 TileRect {
94 x,
95 y,
96 width,
97 height: self.tile_height,
98 }
99 }
100
101 fn estimated_row_height(&self) -> f32 {
102 self.tile_height
103 }
104
105 fn index_at_point(
106 &self,
107 content_point: Point,
108 item_count: usize,
109 viewport_width: f32,
110 ) -> Option<usize> {
111 if item_count == 0 || self.row_step() <= 0.0 {
112 return None;
113 }
114 let y = content_point.y - self.inset.top;
115 if y < 0.0 {
116 return None;
117 }
118 let row = (y / self.row_step()) as usize;
119 if y - row as f32 * self.row_step() > self.tile_height {
120 return None; }
122 let col = column_at(&self.columns, content_point.x, viewport_width)?;
123 let cols = self.column_count(viewport_width);
124 let idx = row * cols + col;
125 (idx < item_count).then_some(idx)
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 fn grid() -> UniformGrid {
134 UniformGrid::new(
136 GridSizing::Fixed {
137 width: 100.0,
138 height: 50.0,
139 },
140 10.0,
141 10.0,
142 EdgeInsets::ZERO,
143 )
144 }
145
146 #[test]
147 fn index_at_point_closed_form_matches_exact_edges() {
148 let g = grid();
149 assert_eq!(g.index_at_point(Point::new(0.0, 0.0), 12, 430.0), Some(0));
151 assert_eq!(
152 g.index_at_point(Point::new(100.0, 50.0), 12, 430.0),
153 Some(0)
154 );
155 assert_eq!(g.index_at_point(Point::new(0.0, 60.0), 12, 430.0), Some(4));
157 }
158
159 #[test]
160 fn index_at_point_closed_form_returns_none_in_gaps() {
161 let g = grid();
162 assert_eq!(g.index_at_point(Point::new(50.0, 55.0), 12, 430.0), None);
164 assert_eq!(g.index_at_point(Point::new(105.0, 25.0), 12, 430.0), None);
166 assert_eq!(g.index_at_point(Point::new(0.0, 9000.0), 12, 430.0), None);
168 assert_eq!(g.index_at_point(Point::new(0.0, -5.0), 12, 430.0), None);
170 }
171}