1use crossterm::event::KeyCode;
2use unicode_width::UnicodeWidthStr;
3
4use crate::canvas::*;
5
6use std::rc::Rc;
7
8use super::*;
9
10pub type DataGridSelectionChangedCallback = dyn Fn(&mut DataGrid);
11pub type DataGridKeyHandler = fn(&mut DataGrid, event: &mut WindowEvent);
12
13#[derive(Debug)]
14pub enum DataValue<'a> {
15 String(&'a str),
16 Integer(i32),
17 Boolean(bool),
18 Float(f32),
19}
20
21impl<'a> DataValue<'a> {
22 pub fn width(&self, radix: Radix) -> TSize {
25 match self {
26 Self::String(s) => s.width() as TSize,
27 Self::Integer(i) => {
28 i.abs().checked_ilog(radix as i32).unwrap_or(0) as TSize
29 + 1 + i.is_negative() as TSize
30 + radix.prefix_glyphs().len() as TSize
31 }
32 Self::Float(f) => {
33 if f.is_nan() {
34 return 3;
35 }
36 else if *f == f32::INFINITY {
37 return 1;
38 }
39 else if *f == f32::NEG_INFINITY {
40 return 2;
41 }
42 let sign = (f.is_sign_negative() && *f != 0.0) as TSize;
43 let fract = {
44 let mut f2 = f.fract();
45 let mut cnt = 0;
46 while f2 != 0.0 {
47 f2 = (f2 * radix as usize as f32).fract();
48 cnt += 1;
49 }
50 cnt + 1
51 };
52 let int = (f.trunc() as i32)
53 .abs()
54 .checked_ilog(radix as i32)
55 .unwrap_or(0) as TSize
56 + 1;
57 sign + fract + int + radix.prefix_glyphs().len() as TSize
58 }
59 Self::Boolean(_) => 3,
60 }
61 }
62}
63
64pub trait DataRow {
65 fn get_data<'a>(&'a self, column: usize) -> Option<DataValue<'a>>;
68}
69
70pub trait DataSource {
71 fn column_definitions(&self) -> &[ColumnDefiniton];
73
74 fn rows(&self) -> usize;
76
77 fn get_row(&self, row: usize) -> &dyn DataRow;
80}
81
82#[derive(Default)]
83struct EmptyRow {}
84
85impl DataRow for EmptyRow {
86 fn get_data<'a>(&'a self, _column: usize) -> Option<DataValue<'a>> {
87 None
88 }
89}
90
91#[derive(Default)]
92struct EmptyDataSource(EmptyRow);
93
94impl DataSource for EmptyDataSource {
95 fn column_definitions(&self) -> &[ColumnDefiniton] {
96 &[]
97 }
98
99 fn rows(&self) -> usize {
100 usize::MIN
101 }
102
103 fn get_row(&self, _row: usize) -> &dyn DataRow {
104 &self.0
105 }
106}
107
108
109#[derive(Clone, PartialEq)]
110pub struct ColumnDefiniton {
111 name: String,
112 header_width: TSize,
113}
114
115impl ColumnDefiniton {
116 pub fn new<S: ToString>(name: S) -> Self {
117 let n = name.to_string();
118 let width = n.width() as TSize;
119 Self {
120 name: n,
121 header_width: width,
122 }
123 }
124}
125
126pub struct DataGrid {
137 base: WidgetBase,
138 data: Box<dyn DataSource>,
139 offset: Vector2D<usize>,
140 selection: Vector2D<usize>,
141 render_size: Vector2D<TSize>,
142 callback: Rc<DataGridSelectionChangedCallback>,
143 avail_size: TPoint,
144 col_widths: Vec<TSize>,
145 show_cols: usize,
146 key_handler: DataGridKeyHandler,
147 radix: Radix,
148 sel_color: Option<Color>,
149}
150
151impl Default for DataGrid {
152 fn default() -> Self {
153 Self::new()
154 }
155}
156
157impl DataGrid {
158 pub fn new() -> Self {
159 let mut s = Self {
160 base: WidgetBase::default(),
161 data: Box::new(EmptyDataSource::default()),
162 offset: Vector2D::default(),
163 selection: Vector2D::default(),
164 render_size: Vector2D::default(),
165 avail_size: Default::default(),
166 col_widths: Vec::default(),
167 show_cols: 0,
168 radix: Radix::default(),
169 sel_color: None,
170 callback: Rc::new(Self::default_callback),
171 key_handler: Self::default_key_handler,
172 };
173 s.base.constraints.y.min = NonZero::new(3).unwrap();
174 s.base.constraints.y.max = Size::Relative(Percent::from_int(100));
175 s
176 }
177
178 pub fn default_callback(_: &mut Self) {}
179
180 pub fn default_key_handler(&mut self, event: &mut WindowEvent) {
181 if let Event::Key(k) = event.raw() {
182 match k.code {
183 KeyCode::Up => self.move_selection_up(),
184 KeyCode::Down => self.move_selection_down(),
185 KeyCode::Left => self.move_selection_left(),
186 KeyCode::Right => self.move_selection_right(),
187 KeyCode::PageUp => self.move_page_up(),
188 KeyCode::PageDown => self.move_page_down(),
189 _ => {}
190 }
191 }
192 }
193
194 pub fn set_key_handler(&mut self, f: DataGridKeyHandler) {
195 self.key_handler = f;
196 }
197
198 pub fn set_callback<F: Fn(&mut Self) + 'static>(&mut self, callback: F) {
199 self.callback = Rc::new(callback);
200 }
201
202 pub fn set_radix(&mut self, radix: Radix) {
203 self.radix = radix;
204 self.reevaluate_columns();
205 }
206
207 pub fn set_selection_color(&mut self, color: Option<Color>) {
208 self.sel_color = color;
209 }
210
211 pub fn move_selection_up(&mut self) {
212 if self.selection.y > usize::MIN {
213 self.selection.y -= 1;
214 if self.offset.y > self.selection.y {
215 self.offset.y -= 1;
216 }
217 self.selection_changed();
218 }
219 }
220
221 pub fn move_selection_down(&mut self) {
222 if self.selection.y < self.data.rows() - 1 {
223 self.selection.y += 1;
224 if self.selection.y >= self.rendered_rows() + self.offset.y {
225 self.offset.y += 1;
226 }
227 self.selection_changed();
228 }
229 }
230
231 pub fn move_selection_left(&mut self) {
232 if self.selection.x > usize::MIN {
233 self.selection.x -= 1;
234 if self.offset.x > self.selection.x {
235 self.offset.x -= 1;
236 self.reevaluate_columns_range(true);
237 }
238 self.selection_changed();
239 }
240 }
241
242 pub fn move_selection_right(&mut self) {
243 if self.selection.x < self.data.column_definitions().len() - 1 {
244 self.selection.x += 1;
245 if self.selection.x > self.show_cols - 1 {
246 self.offset.x += 1;
247 self.reevaluate_columns_range(true);
248 }
249 self.selection_changed();
250 }
251 }
252
253 fn selection_changed(&mut self) {
254 let cb = self.callback.clone();
255 cb(self);
256 }
257
258 pub fn move_page_up(&mut self) {
259 self.selection.y = self.selection.y.saturating_sub(self.rendered_rows());
260 self.offset.y = self.offset.y.saturating_sub(self.rendered_rows());
261 self.selection_changed();
262 }
263
264 fn rendered_rows(&self) -> usize {
265 (self.avail_size.y as usize).saturating_sub(2)
266 }
267
268 pub fn move_page_down(&mut self) {
269 let y = self.selection.y + self.rendered_rows();
270 if y >= self.data.rows() {
271 self.selection.y = self.data.rows() - 1;
272 self.offset.y = self.data.rows().saturating_sub(self.rendered_rows());
273 self.selection_changed();
274 }
275 else {
276 self.selection.y = y;
277 self.offset.y += self.rendered_rows();
278 if self.offset.y + self.rendered_rows() > self.data.rows() {
279 self.offset.y = self.data.rows().saturating_sub(self.rendered_rows());
280 }
281 self.selection_changed();
282 }
283 }
284
285 fn reevaluate_columns_range(&mut self, provoke: bool) {
286 self.offset.x = 0;
287 self.show_cols = self.col_widths.len();
288 loop {
289 let left = self.selection.x != self.offset.x;
290 let width = self.col_widths[self.offset.x..self.show_cols]
291 .iter()
292 .sum::<TSize>()
293 + self.show_cols as TSize
294 - 1;
295 if width > self.avail_size.x {
296 if left {
297 self.offset.x += 1;
298 }
299 else {
300 self.show_cols -= 1;
301 }
302 }
303 else {
304 break;
305 }
306 }
307
308 if !(self.offset.y..self.offset.y + self.rendered_rows()).contains(&self.selection.y) {
309 self.offset.y = self.selection.y.saturating_sub(self.rendered_rows() - 1);
310 }
311
312 let old = self.base.constraints.x;
313 if !self.col_widths.is_empty() {
314 self.base.constraints.x.min = NonZero::new(self.col_widths[self.offset.x]).unwrap();
315 self.base.constraints.x.max = Size::Fixed(
316 NonZero::new(
317 self.col_widths[self.offset.x..self.show_cols]
318 .iter()
319 .sum::<TSize>() + self.col_widths.len() as TSize
320 - 1,
321 )
322 .unwrap(),
323 );
324 }
325 else {
326 self.base.constraints.x.max = Size::Relative(Percent::from_int(100));
327 }
328
329 if provoke && self.base.constraints.x != old {
330 self.provoke_changed_property(WindowProperty::Size);
331 }
332 }
333
334 pub fn reevaluate_columns(&mut self) {
336 self.col_widths.fill(0);
337 self.col_widths
338 .resize(self.data.column_definitions().len(), 0);
339 for (x, max) in self.col_widths.iter_mut().enumerate() {
340 for y in 0..self.data.rows() {
341 let row = self.data.get_row(y);
342 if let Some(data) = row.get_data(x) {
343 let w = data.width(self.radix);
344 if w > *max {
345 *max = w;
346 }
347 }
348 }
349
350 let c_name_w = self.data.column_definitions()[x].header_width;
351 if c_name_w > *max {
352 *max = c_name_w;
353 }
354 }
355
356 if self.render_size != Vector2D::new(0, 0) {
357 self.reevaluate_columns_range(true);
358 }
359 }
360
361 pub fn set_data_source(&mut self, data: Box<dyn DataSource>) {
363 self.data = data;
364 self.reevaluate_columns();
365 }
366}
367
368impl Window for DataGrid {
369 fn render(&self, canvas: &mut crate::canvas::Canvas) {
370 let mut current_width = 0;
372 for idx in self.offset.x..self.show_cols {
373 let c = &self.data.column_definitions()[idx];
374 let mut row0 = canvas.get_row_variable_width(0).unwrap();
375 let mut row1 = canvas.get_row(1, GlyphWidth::Half).unwrap();
376 let next_w = self.col_widths[idx];
377 row0.skip_until(current_width);
378 row0.add_string(&c.name, None, None, Style::None, None).ok();
379 row0.skip_until(current_width + next_w);
380 if idx != self.show_cols - 1 {
381 row0.add_grapheme(BorderKind::Solid.line_style()[1], None, None, Style::None)
382 .ok();
383 }
384 for x in current_width..current_width + next_w {
385 let mut g1 = row1.get(x).unwrap();
386 g1.set_grapheme(BorderKind::Solid.line_style()[0]).ok();
387 }
388
389 if idx != self.show_cols - 1 {
390 let mut g1 = row1.get(current_width + next_w).unwrap();
391 g1.set_grapheme(BorderKind::Solid.cross_style()).ok();
392 }
393
394 current_width += next_w;
395 current_width += 1;
396 }
397
398 let mut max = self.data.rows();
399 if max > self.rendered_rows() {
400 max = self.rendered_rows();
401 }
402
403 for y in 0..max {
405 let drow = self.data.get_row(y + self.offset.y);
406 let mut row = canvas.get_row_variable_width(2 + y as TSize).unwrap();
407 if let Some(bg) = self.base.colors.base_bg {
408 row.fill_background(bg);
409 }
410 let base_fg = self.base.colors.base_fg;
411 let mut current_width = 0;
412 for x in 0..self.show_cols - self.offset.x {
413 let index = self.offset.x + x;
414 let col = self.col_widths[index];
415 if let Some(value) = drow.get_data(index) {
416 row.skip_until(current_width);
417 if x > 0 {
418 row.add_grapheme(
419 BorderKind::Solid.line_style()[1],
420 base_fg,
421 None,
422 Style::None,
423 )
424 .ok();
425 current_width += 1;
426 }
427
428 let fg = match self.selection == Vector2D::new(x, y) + self.offset {
429 true => self.sel_color.or(base_fg),
430 false => base_fg,
431 };
432
433 match value {
434 DataValue::Float(f) => {
435 for g in self.radix.iter_float(f) {
436 row.add_grapheme(g, fg, None, Style::None).ok();
437 }
438 }
439 DataValue::Boolean(b) => {
440 row.add_grapheme(Checkbox::OPEN_BOX, fg, None, Style::None)
441 .ok();
442 if b {
443 row.add_grapheme(Checkbox::CROSS_MARK, fg, None, Style::None)
444 .ok();
445 }
446 else {
447 row.skip(1);
448 }
449 row.add_grapheme(Checkbox::CLOSE_BOX, fg, None, Style::ResetAfter)
450 .ok();
451 }
452 DataValue::Integer(i) => {
453 for g in self.radix.iter_integer(i) {
454 row.add_grapheme(g, fg, None, Style::None).ok();
455 }
456 }
457 DataValue::String(s) => {
458 row.add_string(s, fg, None, Style::None, None).ok();
459 }
460 }
461 }
462 current_width += col;
463 }
464 }
465 }
466
467 fn handle_event(&mut self, event: &mut WindowEvent) {
468 match event.raw() {
469 Event::Key(_) => (self.key_handler)(self, event),
470 Event::Resize(w, h) => {
471 self.render_size = Vector2D::new(*w, *h);
472 if self.offset.y + self.rendered_rows() > self.data.rows() {
473 self.offset.y = self.data.rows().saturating_sub(self.rendered_rows());
474 }
475 }
476 _ => {}
477 }
478 }
479
480 fn is_enabled(&self) -> bool {
481 self.base.enabled
482 }
483}
484
485
486impl WindowLayout for DataGrid {
487 fn desired_size_pre_hook(&mut self, available_size: TPoint) {
488 if self.avail_size != available_size {
489 self.avail_size = available_size;
490 self.reevaluate_columns_range(false);
491 }
492 }
493
494 fn desired_size(&self, available_size: TPoint) -> TPoint {
495 self.base.desired_size(available_size)
496 }
497
498 fn alignment(&self) -> (HorizontalAlignment, VerticalAlignment) {
499 self.base.alignment
500 }
501
502 fn margin(&self) -> Thickness {
503 self.base.margin
504 }
505
506 fn border(&self) -> BorderStyle {
507 self.base.border
508 }
509
510 fn is_visible(&self) -> bool {
511 self.base.visibility
512 }
513}
514
515impl HasWindowUID for DataGrid {
516 fn uid(&self) -> WindowUID {
517 self.base.uid
518 }
519}
520
521impl Widget for DataGrid {
522 fn set_alignment(
523 &mut self,
524 horizontal: super::HorizontalAlignment,
525 vertical: super::VerticalAlignment,
526 ) {
527 self.base.alignment = (horizontal, vertical);
528 self.provoke_changed_property(WindowProperty::Alignment);
529 }
530
531 fn set_visibility(&mut self, visibility: bool) {
532 self.base.visibility = visibility;
533 self.provoke_changed_property(WindowProperty::IsVisible);
534 }
535
536 fn set_width(&mut self, width: super::Size) {
537 self.base.size.x = width;
538 self.provoke_changed_property(WindowProperty::Size);
539 }
540
541 fn set_height(&mut self, height: super::Size) {
542 self.base.size.y = height;
543 self.provoke_changed_property(WindowProperty::Size);
544 }
545
546 fn set_margin(&mut self, margin: Thickness) {
547 self.base.margin = margin;
548 self.provoke_changed_property(WindowProperty::Margin);
549 }
550
551 fn set_border(&mut self, border: BorderStyle) {
552 self.base.border = border;
553 self.provoke_changed_property(WindowProperty::Border);
554 }
555
556 fn set_enabled_state(&mut self, is_enabled: bool) {
557 self.base.enabled = is_enabled;
558 }
559
560 fn set_width_constraint(&mut self, width: SizeConstraint) {
561 self.base.constraints.x = width;
562 self.provoke_changed_property(WindowProperty::Size);
563 }
564
565 fn set_height_constraint(&mut self, height: SizeConstraint) {
566 self.base.constraints.y = height;
567 self.provoke_changed_property(WindowProperty::Size);
568 }
569}
570
571impl WidgetColors for DataGrid {
572 fn set_disabled_color(&mut self, color: Option<Color>) {
573 self.base.colors.disabled = color;
574 }
575
576 fn set_base_fg_color(&mut self, color: Option<Color>) {
577 self.base.colors.base_fg = color;
578 }
579
580 fn set_base_bg_color(&mut self, color: Option<Color>) {
581 self.base.colors.base_bg = color;
582 }
583}