1use jag_draw::{Brush, ColorLinPremul, Rect};
4use jag_surface::Canvas;
5
6use crate::event::{
7 EventHandler, EventResult, KeyboardEvent, MouseClickEvent, MouseMoveEvent, ScrollEvent,
8};
9use crate::focus::FocusId;
10
11use super::Element;
12
13pub struct Table {
16 pub rect: Rect,
17 pub headers: Vec<String>,
19 pub rows: Vec<Vec<String>>,
21 pub header_bg: ColorLinPremul,
23 pub header_text_color: ColorLinPremul,
25 pub cell_text_color: ColorLinPremul,
27 pub grid_color: ColorLinPremul,
29 pub grid_width: f32,
31 pub bg: ColorLinPremul,
33 pub header_font_size: f32,
35 pub cell_font_size: f32,
37 pub row_height: f32,
39 pub cell_padding_x: f32,
41 pub zebra_striping: bool,
43 pub zebra_color: ColorLinPremul,
45}
46
47impl Table {
48 pub fn new(headers: Vec<String>) -> Self {
50 Self {
51 rect: Rect {
52 x: 0.0,
53 y: 0.0,
54 w: 600.0,
55 h: 300.0,
56 },
57 headers,
58 rows: Vec::new(),
59 header_bg: ColorLinPremul::from_srgba_u8([248, 248, 248, 255]),
60 header_text_color: ColorLinPremul::from_srgba_u8([60, 60, 60, 255]),
61 cell_text_color: ColorLinPremul::from_srgba_u8([80, 80, 80, 255]),
62 grid_color: ColorLinPremul::from_srgba_u8([224, 224, 224, 255]),
63 grid_width: 1.0,
64 bg: ColorLinPremul::from_srgba_u8([255, 255, 255, 255]),
65 header_font_size: 14.0,
66 cell_font_size: 14.0,
67 row_height: 40.0,
68 cell_padding_x: 12.0,
69 zebra_striping: false,
70 zebra_color: ColorLinPremul::from_srgba_u8([249, 249, 249, 255]),
71 }
72 }
73
74 pub fn with_rows(mut self, rows: Vec<Vec<String>>) -> Self {
76 self.rows = rows;
77 self
78 }
79
80 pub fn add_row(&mut self, row: Vec<String>) {
82 self.rows.push(row);
83 }
84
85 pub fn with_zebra(mut self, enabled: bool) -> Self {
87 self.zebra_striping = enabled;
88 self
89 }
90
91 fn column_width(&self) -> f32 {
93 let n = self.headers.len().max(1) as f32;
94 self.rect.w / n
95 }
96
97 pub fn hit_test(&self, x: f32, y: f32) -> bool {
99 x >= self.rect.x
100 && x <= self.rect.x + self.rect.w
101 && y >= self.rect.y
102 && y <= self.rect.y + self.rect.h
103 }
104}
105
106impl Element for Table {
111 fn rect(&self) -> Rect {
112 self.rect
113 }
114
115 fn set_rect(&mut self, rect: Rect) {
116 self.rect = rect;
117 }
118
119 fn render(&self, canvas: &mut Canvas, z: i32) {
120 let col_w = self.column_width();
121 let mut y = self.rect.y;
122
123 canvas.fill_rect(
125 self.rect.x,
126 self.rect.y,
127 self.rect.w,
128 self.rect.h,
129 Brush::Solid(self.bg),
130 z,
131 );
132
133 if !self.headers.is_empty() {
135 canvas.fill_rect(
136 self.rect.x,
137 y,
138 self.rect.w,
139 self.row_height,
140 Brush::Solid(self.header_bg),
141 z + 1,
142 );
143
144 for (i, header) in self.headers.iter().enumerate() {
146 let tx = self.rect.x + i as f32 * col_w + self.cell_padding_x;
147 let ty = y + self.row_height * 0.5 + self.header_font_size * 0.35;
148 canvas.draw_text_run_weighted(
149 [tx, ty],
150 header.clone(),
151 self.header_font_size,
152 600.0,
153 self.header_text_color,
154 z + 3,
155 );
156 }
157
158 canvas.fill_rect(
160 self.rect.x,
161 y + self.row_height,
162 self.rect.w,
163 self.grid_width,
164 Brush::Solid(self.grid_color),
165 z + 2,
166 );
167
168 y += self.row_height;
169 }
170
171 for (row_idx, row) in self.rows.iter().enumerate() {
173 if y > self.rect.y + self.rect.h {
174 break;
175 }
176
177 if self.zebra_striping && row_idx % 2 == 1 {
179 canvas.fill_rect(
180 self.rect.x,
181 y,
182 self.rect.w,
183 self.row_height,
184 Brush::Solid(self.zebra_color),
185 z + 1,
186 );
187 }
188
189 for (col_idx, cell) in row.iter().enumerate() {
191 if col_idx >= self.headers.len() {
192 break;
193 }
194 let tx = self.rect.x + col_idx as f32 * col_w + self.cell_padding_x;
195 let ty = y + self.row_height * 0.5 + self.cell_font_size * 0.35;
196 canvas.draw_text_run_weighted(
197 [tx, ty],
198 cell.clone(),
199 self.cell_font_size,
200 400.0,
201 self.cell_text_color,
202 z + 3,
203 );
204 }
205
206 if row_idx < self.rows.len() - 1 {
208 canvas.fill_rect(
209 self.rect.x,
210 y + self.row_height,
211 self.rect.w,
212 self.grid_width,
213 Brush::Solid(self.grid_color),
214 z + 2,
215 );
216 }
217
218 y += self.row_height;
219 }
220
221 for i in 1..self.headers.len() {
223 let lx = self.rect.x + i as f32 * col_w;
224 canvas.fill_rect(
225 lx,
226 self.rect.y,
227 self.grid_width,
228 (y - self.rect.y).min(self.rect.h),
229 Brush::Solid(self.grid_color),
230 z + 2,
231 );
232 }
233
234 if self.grid_width > 0.0 {
236 let used_h = (y - self.rect.y).min(self.rect.h);
237 canvas.fill_rect(
239 self.rect.x,
240 self.rect.y,
241 self.rect.w,
242 self.grid_width,
243 Brush::Solid(self.grid_color),
244 z + 4,
245 );
246 canvas.fill_rect(
248 self.rect.x,
249 self.rect.y + used_h - self.grid_width,
250 self.rect.w,
251 self.grid_width,
252 Brush::Solid(self.grid_color),
253 z + 4,
254 );
255 canvas.fill_rect(
257 self.rect.x,
258 self.rect.y,
259 self.grid_width,
260 used_h,
261 Brush::Solid(self.grid_color),
262 z + 4,
263 );
264 canvas.fill_rect(
266 self.rect.x + self.rect.w - self.grid_width,
267 self.rect.y,
268 self.grid_width,
269 used_h,
270 Brush::Solid(self.grid_color),
271 z + 4,
272 );
273 }
274 }
275
276 fn focus_id(&self) -> Option<FocusId> {
277 None
278 }
279}
280
281impl EventHandler for Table {
286 fn handle_mouse_click(&mut self, _event: &MouseClickEvent) -> EventResult {
287 EventResult::Ignored
288 }
289
290 fn handle_keyboard(&mut self, _event: &KeyboardEvent) -> EventResult {
291 EventResult::Ignored
292 }
293
294 fn handle_mouse_move(&mut self, _event: &MouseMoveEvent) -> EventResult {
295 EventResult::Ignored
296 }
297
298 fn handle_scroll(&mut self, _event: &ScrollEvent) -> EventResult {
299 EventResult::Ignored
300 }
301
302 fn is_focused(&self) -> bool {
303 false
304 }
305
306 fn set_focused(&mut self, _focused: bool) {}
307
308 fn contains_point(&self, x: f32, y: f32) -> bool {
309 self.hit_test(x, y)
310 }
311}
312
313#[cfg(test)]
318mod tests {
319 use super::*;
320
321 #[test]
322 fn table_new_defaults() {
323 let t = Table::new(vec!["Name".into(), "Age".into()]);
324 assert_eq!(t.headers.len(), 2);
325 assert!(t.rows.is_empty());
326 assert!(!t.zebra_striping);
327 }
328
329 #[test]
330 fn table_add_rows() {
331 let mut t = Table::new(vec!["A".into(), "B".into()]);
332 t.add_row(vec!["1".into(), "2".into()]);
333 t.add_row(vec!["3".into(), "4".into()]);
334 assert_eq!(t.rows.len(), 2);
335 }
336
337 #[test]
338 fn table_with_rows_builder() {
339 let t = Table::new(vec!["X".into()]).with_rows(vec![vec!["a".into()], vec!["b".into()]]);
340 assert_eq!(t.rows.len(), 2);
341 }
342
343 #[test]
344 fn table_column_width() {
345 let mut t = Table::new(vec!["A".into(), "B".into(), "C".into()]);
346 t.rect.w = 300.0;
347 let cw = t.column_width();
348 assert!((cw - 100.0).abs() < f32::EPSILON);
349 }
350
351 #[test]
352 fn table_hit_test() {
353 let t = Table::new(vec!["H".into()]);
354 assert!(t.hit_test(300.0, 150.0));
355 assert!(!t.hit_test(700.0, 0.0));
356 }
357
358 #[test]
359 fn table_not_focusable() {
360 let t = Table::new(vec!["H".into()]);
361 assert!(t.focus_id().is_none());
362 }
363}