fltk_table/
lib.rs

1#![doc = include_str!("../README.md")]
2#![allow(clippy::needless_doctest_main)]
3
4use fltk::{
5    app, draw,
6    enums::*,
7    input,
8    prelude::{GroupExt, InputExt, TableExt, WidgetBase, WidgetExt},
9    table, window,
10};
11use std::cell::RefCell;
12use std::rc::Rc;
13use std::sync::{Arc, Mutex};
14
15#[derive(Debug, Default, Clone)]
16pub struct Cell {
17    pub label: String,
18    pub color: Option<Color>,
19    pub font: Option<Font>,
20    pub font_color: Option<Color>,
21    pub font_size: Option<i32>,
22    pub selection_color: Option<Color>,
23    pub align: Option<Align>,
24    pub border_color: Option<Color>,
25}
26
27impl Cell {
28    fn with_label(l: &str) -> Cell {
29        Cell {
30            label: l.to_string(),
31            ..Default::default()
32        }
33    }
34}
35
36type CellMatrix = Vec<Vec<Cell>>;
37
38// Needed to store cell information during the draw_cell call
39#[derive(Default)]
40struct CellData {
41    pub row: i32, // row
42    pub col: i32, // column
43    pub x: i32,
44    pub y: i32,
45    pub w: i32,
46    pub h: i32,
47}
48
49impl CellData {
50    fn select(&mut self, row: i32, col: i32, x: i32, y: i32, w: i32, h: i32) {
51        self.row = row;
52        self.col = col;
53        self.x = x;
54        self.y = y;
55        self.w = w;
56        self.h = h;
57    }
58}
59
60/// Contains the parameters for our table, including rows, columns and other styling params
61#[derive(Debug, Clone, Copy)]
62pub struct TableOpts {
63    pub rows: i32,
64    pub cols: i32,
65    pub editable: bool,
66    pub cell_color: Color,
67    pub cell_font: Font,
68    pub cell_font_color: Color,
69    pub cell_font_size: i32,
70    pub cell_selection_color: Color,
71    pub cell_align: Align,
72    pub cell_border_color: Color,
73    pub cell_padding: i32,
74    pub header_font: Font,
75    pub header_frame: FrameType,
76    pub header_color: Color,
77    pub header_font_color: Color,
78    pub header_font_size: i32,
79    pub header_align: Align,
80}
81
82impl Default for TableOpts {
83    fn default() -> Self {
84        Self {
85            rows: 1,
86            cols: 1,
87            editable: false,
88            cell_color: Color::BackGround2,
89            cell_font: Font::Helvetica,
90            cell_font_color: Color::Gray0,
91            cell_font_size: 14,
92            cell_selection_color: Color::from_u32(0x00D3_D3D3),
93            cell_align: Align::Center,
94            cell_border_color: Color::Gray0,
95            cell_padding: 1,
96            header_font: Font::Helvetica,
97            header_frame: FrameType::ThinUpBox,
98            header_color: Color::FrameDefault,
99            header_font_color: Color::Black,
100            header_font_size: 14,
101            header_align: Align::Center,
102        }
103    }
104}
105
106/// Smart table widget
107#[derive(Clone)]
108pub struct SmartTable {
109    table: table::TableRow,
110    inp: Option<input::Input>,
111    data: Arc<Mutex<CellMatrix>>,
112    row_headers: Arc<Mutex<Vec<String>>>,
113    col_headers: Arc<Mutex<Vec<String>>>,
114    on_update_callback: Arc<Mutex<Box<dyn FnMut(i32, i32, String) + Send>>>,
115}
116
117impl Default for SmartTable {
118    fn default() -> Self {
119        Self::new(0, 0, 0, 0, None)
120    }
121}
122
123impl SmartTable {
124    /// Construct a new SmartTable widget using coords, size and label
125    pub fn new<S: Into<Option<&'static str>>>(x: i32, y: i32, w: i32, h: i32, label: S) -> Self {
126        let table = table::TableRow::new(x, y, w, h, label);
127        table.end();
128        let inp = None;
129        let on_update_callback: Box<dyn FnMut(i32, i32, String) + Send> = Box::new(|_, _, _| ());
130        let on_update_callback = Arc::new(Mutex::new(on_update_callback));
131
132        Self {
133            table,
134            inp,
135            data: Default::default(),
136            row_headers: Default::default(),
137            col_headers: Default::default(),
138            on_update_callback,
139        }
140    }
141
142    /// Create a SmartTable the size of the parent widget
143    pub fn default_fill() -> Self {
144        Self::new(0, 0, 0, 0, None)
145            .size_of_parent()
146            .center_of_parent()
147    }
148
149    /// Sets the tables options
150    pub fn set_opts(&mut self, opts: TableOpts) {
151        let mut data = self.data.try_lock().unwrap();
152        data.resize(opts.rows as _, vec![]);
153        for v in data.iter_mut() {
154            v.resize(opts.cols as _, Cell::default());
155        }
156        drop(data);
157
158        let mut row_headers = vec![];
159        for i in 0..opts.rows {
160            row_headers.push((i + 1).to_string());
161        }
162        let row_headers = Arc::new(Mutex::new(row_headers));
163        self.row_headers = row_headers;
164
165        let mut col_headers = vec![];
166        for i in 0..opts.cols {
167            let mut pref = String::new();
168            if i > 25 {
169                let t = (i / 26) as i32;
170                if t > 26 {
171                    col_headers.push(i.to_string());
172                } else {
173                    pref.push((t - 1 + 65) as u8 as char);
174                    col_headers.push(format!("{}{}", pref, (i - (26 * t) + 65) as u8 as char));
175                }
176            } else {
177                col_headers.push(format!("{}", (i + 65) as u8 as char));
178            }
179        }
180        let col_headers = Arc::new(Mutex::new(col_headers));
181        self.col_headers = col_headers;
182
183        let len = opts.rows;
184        let inner_len = opts.cols;
185
186        let cell = Rc::from(RefCell::from(CellData::default()));
187        self.table.set_rows(len as i32);
188        self.table.set_cols(inner_len as i32);
189        self.table.set_row_header(true);
190        self.table.set_row_resize(true);
191        self.table.set_col_header(true);
192        self.table.set_col_resize(true);
193        self.table.end();
194
195        // Called when the table is drawn then when it's redrawn due to events
196        self.table.draw_cell({
197            let cell = cell.clone();
198            let data = self.data.clone();
199            let row_headers = self.row_headers.clone();
200            let col_headers = self.col_headers.clone();
201            move |t, ctx, row, col, x, y, w, h| {
202                if let Ok(data) = data.try_lock() {
203                    let row_headers = row_headers.try_lock().unwrap();
204                    let col_headers = col_headers.try_lock().unwrap();
205                    match ctx {
206                        table::TableContext::StartPage => draw::set_font(Font::Helvetica, 14),
207                        table::TableContext::ColHeader => {
208                            Self::draw_header(&col_headers[col as usize], x, y, w, h, &opts)
209                        } // Column titles
210                        table::TableContext::RowHeader => {
211                            Self::draw_header(&row_headers[row as usize], x, y, w, h, &opts)
212                        } // Row titles
213                        table::TableContext::Cell => {
214                            if t.is_selected(row, col) {
215                                cell.borrow_mut().select(row, col, x, y, w, h); // Captures the cell information
216                            }
217                            Self::draw_data(
218                                &data[row as usize][col as usize],
219                                x,
220                                y,
221                                w,
222                                h,
223                                t.is_selected(row, col),
224                                &opts,
225                            );
226                        }
227                        _ => (),
228                    }
229                }
230            }
231        });
232
233        if opts.editable {
234            self.inp = Some(input::Input::default());
235            let mut inp = self.inp.as_ref().unwrap().clone();
236            inp.set_trigger(CallbackTrigger::EnterKey);
237            let win = window::Window::from_dyn_widget_ptr(
238                self.table.top_window().unwrap().as_widget_ptr(),
239            );
240            win.unwrap().add(&inp);
241            inp.hide();
242
243            inp.set_callback({
244                let cell = cell.clone();
245                let data = self.data.clone();
246                let mut table = self.table.clone();
247                let on_update_callback = self.on_update_callback.clone();
248                move |i| {
249                    let cell = cell.borrow();
250                    on_update_callback.try_lock().unwrap()(cell.row, cell.col, i.value());
251                    data.try_lock().unwrap()[cell.row as usize][cell.col as usize].label =
252                        i.value();
253                    i.set_value("");
254                    i.hide();
255                    table.redraw();
256                }
257            });
258
259            inp.handle(|i, ev| match ev {
260                Event::KeyUp => {
261                    if app::event_key() == Key::Escape {
262                        i.hide();
263                        true
264                    } else {
265                        false
266                    }
267                }
268                _ => false,
269            });
270
271            self.table.handle({
272                let data = self.data.clone();
273                move |_, ev| match ev {
274                    Event::Released => {
275                        if let Ok(data) = data.try_lock() {
276                            let cell = cell.borrow();
277                            inp.resize(cell.x, cell.y, cell.w, cell.h);
278                            inp.set_value(&data[cell.row as usize][cell.col as usize].label);
279                            inp.show();
280                            inp.take_focus().ok();
281                            inp.redraw();
282                            true
283                        } else {
284                            false
285                        }
286                    }
287                    _ => false,
288                }
289            });
290        }
291    }
292
293    /// Instantiate with TableOpts
294    pub fn with_opts(mut self, opts: TableOpts) -> Self {
295        self.set_opts(opts);
296        self
297    }
298
299    /// Get the input widget
300    pub fn input(&mut self) -> &mut Option<input::Input> {
301        &mut self.inp
302    }
303
304    /// Get a copy of the data
305    pub fn data(&self) -> CellMatrix {
306        self.data.try_lock().unwrap().clone()
307    }
308
309    /// Get the inner data
310    pub fn data_ref(&self) -> Arc<Mutex<CellMatrix>> {
311        self.data.clone()
312    }
313
314    fn draw_header(txt: &str, x: i32, y: i32, w: i32, h: i32, opts: &TableOpts) {
315        draw::push_clip(x, y, w, h);
316        draw::draw_box(opts.header_frame, x, y, w, h, opts.header_color);
317        draw::set_draw_color(opts.header_font_color);
318        draw::set_font(opts.header_font, opts.header_font_size);
319        draw::draw_text2(txt, x, y, w, h, opts.header_align);
320        draw::pop_clip();
321    }
322
323    // The selected flag sets the color of the cell to a grayish color, otherwise white
324    fn draw_data(cell: &Cell, x: i32, y: i32, w: i32, h: i32, selected: bool, opts: &TableOpts) {
325        draw::push_clip(x, y, w, h);
326        let sel_col = if let Some(sel_col) = cell.selection_color {
327            sel_col
328        } else {
329            opts.cell_selection_color
330        };
331        let bg = if let Some(col) = cell.color {
332            col
333        } else {
334            opts.cell_color
335        };
336        if selected {
337            draw::set_draw_color(sel_col);
338        } else {
339            draw::set_draw_color(bg);
340        }
341        draw::draw_rectf(x, y, w, h);
342        draw::set_draw_color(if let Some(col) = cell.font_color {
343            col
344        } else {
345            opts.cell_font_color
346        });
347        draw::set_font(
348            if let Some(font) = cell.font {
349                font
350            } else {
351                opts.cell_font
352            },
353            if let Some(font) = cell.font_size {
354                font
355            } else {
356                opts.cell_font_size
357            },
358        );
359        draw::draw_text2(
360            &cell.label,
361            x + opts.cell_padding,
362            y,
363            w - opts.cell_padding * 2,
364            h,
365            if let Some(a) = cell.align {
366                a
367            } else {
368                opts.cell_align
369            },
370        );
371        draw::set_draw_color(if let Some(col) = cell.border_color {
372            col
373        } else {
374            opts.cell_border_color
375        });
376        draw::draw_rect(x, y, w, h);
377        draw::pop_clip();
378    }
379
380    /// Set the cell value, using the row and column to index the data
381    pub fn set_cell_value(&mut self, row: i32, col: i32, val: &str) {
382        self.data.try_lock().unwrap()[row as usize][col as usize].label = val.to_string();
383    }
384
385    /// Get the cell value, using the row and column to index the data
386    pub fn cell_value(&self, row: i32, col: i32) -> String {
387        self.data.try_lock().unwrap()[row as usize][col as usize]
388            .label
389            .clone()
390    }
391
392    /// Set the cell value, using the row and column to index the data
393    pub fn set_cell_color(&mut self, row: i32, col: i32, color: Color) {
394        self.data.try_lock().unwrap()[row as usize][col as usize].color = Some(color);
395    }
396
397    /// Set the cell value, using the row and column to index the data
398    pub fn set_cell_selection_color(&mut self, row: i32, col: i32, color: Color) {
399        self.data.try_lock().unwrap()[row as usize][col as usize].selection_color = Some(color);
400    }
401
402    /// Set the cell value, using the row and column to index the data
403    pub fn set_cell_font_color(&mut self, row: i32, col: i32, color: Color) {
404        self.data.try_lock().unwrap()[row as usize][col as usize].font_color = Some(color);
405    }
406
407    /// Set the cell value, using the row and column to index the data
408    pub fn set_cell_border_color(&mut self, row: i32, col: i32, color: Color) {
409        self.data.try_lock().unwrap()[row as usize][col as usize].border_color = Some(color);
410    }
411
412    /// Set the cell value, using the row and column to index the data
413    pub fn set_cell_font(&mut self, row: i32, col: i32, font: Font) {
414        self.data.try_lock().unwrap()[row as usize][col as usize].font = Some(font);
415    }
416
417    /// Set the cell value, using the row and column to index the data
418    pub fn set_cell_font_size(&mut self, row: i32, col: i32, sz: i32) {
419        self.data.try_lock().unwrap()[row as usize][col as usize].font_size = Some(sz);
420    }
421
422    /// Set the cell value, using the row and column to index the data
423    pub fn set_cell_align(&mut self, row: i32, col: i32, align: Align) {
424        self.data.try_lock().unwrap()[row as usize][col as usize].align = Some(align);
425    }
426
427    /// Set the row header value at the row index
428    pub fn set_row_header_value(&mut self, row: i32, val: &str) {
429        self.row_headers.try_lock().unwrap()[row as usize] = val.to_string();
430    }
431
432    /// Set the column header value at the column index
433    pub fn set_col_header_value(&mut self, col: i32, val: &str) {
434        self.col_headers.try_lock().unwrap()[col as usize] = val.to_string();
435    }
436
437    /// Get the row header value at the row index
438    pub fn row_header_value(&mut self, row: i32) -> String {
439        self.row_headers.try_lock().unwrap()[row as usize].clone()
440    }
441
442    /// Get the column header value at the column index
443    pub fn col_header_value(&mut self, col: i32) -> String {
444        self.col_headers.try_lock().unwrap()[col as usize].clone()
445    }
446
447    /// Insert an empty row at the row index
448    pub fn insert_empty_row(&mut self, row: i32, row_header: &str) {
449        let mut data = self.data.try_lock().unwrap();
450        let cols = self.column_count() as usize;
451        data.insert(row as _, vec![]);
452        data[row as usize].resize(cols as _, Cell::default());
453        self.row_headers
454            .try_lock()
455            .unwrap()
456            .insert(row as _, row_header.to_string());
457        self.table.set_rows(self.table.rows() + 1);
458    }
459
460    /// Append a row to your table
461    pub fn insert_row(&mut self, row: i32, row_header: &str, vals: &[&str]) {
462        let mut data = self.data.try_lock().unwrap();
463        let cols = self.column_count() as usize;
464        assert!(cols == vals.len());
465        data.insert(row as _, vals.iter().map(|v| Cell::with_label(v)).collect());
466        self.row_headers
467            .try_lock()
468            .unwrap()
469            .push(row_header.to_string());
470        self.table.set_rows(self.table.rows() + 1);
471    }
472
473    /// Append an empty row to your table
474    pub fn append_empty_row(&mut self, row_header: &str) {
475        let mut data = self.data.try_lock().unwrap();
476        let cols = self.column_count() as usize;
477        data.push(vec![]);
478        data.last_mut().unwrap().resize(cols as _, Cell::default());
479        self.row_headers
480            .try_lock()
481            .unwrap()
482            .push(row_header.to_string());
483        self.table.set_rows(self.table.rows() + 1);
484    }
485
486    /// Append a row to your table
487    pub fn append_row(&mut self, row_header: &str, vals: &[&str]) {
488        let mut data = self.data.try_lock().unwrap();
489        let cols = self.column_count() as usize;
490        assert!(cols == vals.len());
491        data.push(vals.iter().map(|v| Cell::with_label(v)).collect());
492        self.row_headers
493            .try_lock()
494            .unwrap()
495            .push(row_header.to_string());
496        self.table.set_rows(self.table.rows() + 1);
497    }
498
499    /// Insert an empty column at the column index
500    pub fn insert_empty_col(&mut self, col: i32, col_header: &str) {
501        let mut data = self.data.try_lock().unwrap();
502        for v in data.iter_mut() {
503            v.insert(col as _, Cell::default());
504        }
505        self.col_headers
506            .try_lock()
507            .unwrap()
508            .insert(col as _, col_header.to_string());
509        self.table.set_cols(self.table.cols() + 1);
510    }
511
512    /// Append a column to your table
513    pub fn insert_col(&mut self, col: i32, col_header: &str, vals: &[&str]) {
514        let mut data = self.data.try_lock().unwrap();
515        assert!(vals.len() == self.table.rows() as usize);
516        let mut count = 0;
517        for v in data.iter_mut() {
518            v.insert(col as _, Cell::with_label(vals[count]));
519            count += 1;
520        }
521        self.col_headers
522            .try_lock()
523            .unwrap()
524            .push(col_header.to_string());
525        self.table.set_cols(self.table.cols() + 1);
526    }
527
528    /// Append an empty column to your table
529    pub fn append_empty_col(&mut self, col_header: &str) {
530        let mut data = self.data.try_lock().unwrap();
531        for v in data.iter_mut() {
532            v.push(Cell::default());
533        }
534        self.col_headers
535            .try_lock()
536            .unwrap()
537            .push(col_header.to_string());
538        self.table.set_cols(self.table.cols() + 1);
539    }
540
541    /// Append a column to your table
542    pub fn append_col(&mut self, col_header: &str, vals: &[&str]) {
543        let mut data = self.data.try_lock().unwrap();
544        assert!(vals.len() == self.table.rows() as usize);
545        let mut count = 0;
546        for v in data.iter_mut() {
547            v.push(Cell::with_label(vals[count]));
548            count += 1;
549        }
550        self.col_headers
551            .try_lock()
552            .unwrap()
553            .push(col_header.to_string());
554        self.table.set_cols(self.table.cols() + 1);
555    }
556
557    /// Remove a row at the row index
558    pub fn remove_row(&mut self, row: i32) {
559        let mut data = self.data.try_lock().unwrap();
560        data.remove(row as _);
561        self.row_headers.try_lock().unwrap().remove(row as _);
562        self.table.set_rows(self.table.rows() - 1);
563    }
564
565    /// Remove a column at the column index
566    pub fn remove_col(&mut self, col: i32) {
567        let mut data = self.data.try_lock().unwrap();
568        for v in data.iter_mut() {
569            v.remove(col as _);
570        }
571        self.col_headers.try_lock().unwrap().remove(col as _);
572        self.table.set_cols(self.table.cols() - 1);
573    }
574
575    /// Set a callback for the SmartTable
576    pub fn set_callback<F: FnMut(&mut Self) + 'static>(&mut self, mut cb: F) {
577        let mut s = self.clone();
578        self.table.set_callback(move |_| {
579            cb(&mut s);
580        });
581    }
582
583    /// Set a callback for on user input
584    /// callback function takes the values row, col, and the new value of the cell
585    pub fn set_on_update_callback<F: FnMut(i32, i32, String) + Send + 'static>(&mut self, cb: F) {
586        *self.on_update_callback.try_lock().unwrap() = Box::new(cb);
587    }
588
589    /// Clears all cells in the table
590    pub fn clear(&mut self) {
591        let mut data = self.data.try_lock().unwrap();
592        for v in data.iter_mut() {
593            for c in v.iter_mut() {
594                *c = Cell::default();
595            }
596        }
597    }
598
599    /// Returns the row count
600    pub fn row_count(&self) -> i32 {
601        self.table.rows()
602    }
603
604    /// Returns the column count
605    pub fn column_count(&self) -> i32 {
606        self.table.cols()
607    }
608
609    /// Get the column's width
610    pub fn col_width(&self, col: i32) -> i32 {
611        self.table.col_width(col)
612    }
613
614    /// Get the row's height
615    pub fn row_height(&self, row: i32) -> i32 {
616        self.table.row_height(row)
617    }
618
619    /// Set column's width
620    pub fn set_col_width(&mut self, col: i32, width: i32) {
621        self.table.set_col_width(col, width);
622    }
623
624    /// Set the row's height
625    pub fn set_row_height(&mut self, row: i32, height: i32) {
626        self.table.set_row_height(row, height);
627    }
628
629    /// Get the column header height
630    pub fn col_header_height(&self) -> i32 {
631        self.table.col_header_height()
632    }
633
634    /// Get the row header width
635    pub fn row_header_width(&self) -> i32 {
636        self.table.row_header_width()
637    }
638
639    /// Set column header height
640    pub fn set_col_header_height(&mut self, height: i32) {
641        self.table.set_col_header_height(height);
642    }
643
644    /// Set the row header width
645    pub fn set_row_header_width(&mut self, width: i32) {
646        self.table.set_row_header_width(width);
647    }
648}
649
650impl std::fmt::Debug for SmartTable {
651    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
652        fmt.debug_struct("SmartTable")
653            .field("table", &self.table)
654            .field("data", &self.data)
655            .field("row_headers", &self.row_headers)
656            .field("col_headers", &self.col_headers)
657            .finish()
658    }
659}
660
661fltk::widget_extends!(SmartTable, table::TableRow, table);