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#[derive(Default)]
40struct CellData {
41 pub row: i32, pub col: i32, 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#[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#[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 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 pub fn default_fill() -> Self {
144 Self::new(0, 0, 0, 0, None)
145 .size_of_parent()
146 .center_of_parent()
147 }
148
149 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 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 } table::TableContext::RowHeader => {
211 Self::draw_header(&row_headers[row as usize], x, y, w, h, &opts)
212 } table::TableContext::Cell => {
214 if t.is_selected(row, col) {
215 cell.borrow_mut().select(row, col, x, y, w, h); }
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 pub fn with_opts(mut self, opts: TableOpts) -> Self {
295 self.set_opts(opts);
296 self
297 }
298
299 pub fn input(&mut self) -> &mut Option<input::Input> {
301 &mut self.inp
302 }
303
304 pub fn data(&self) -> CellMatrix {
306 self.data.try_lock().unwrap().clone()
307 }
308
309 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 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 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 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 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 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 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 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 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 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 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 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 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 pub fn row_header_value(&mut self, row: i32) -> String {
439 self.row_headers.try_lock().unwrap()[row as usize].clone()
440 }
441
442 pub fn col_header_value(&mut self, col: i32) -> String {
444 self.col_headers.try_lock().unwrap()[col as usize].clone()
445 }
446
447 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 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 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 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 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 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 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 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 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 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 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 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 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 pub fn row_count(&self) -> i32 {
601 self.table.rows()
602 }
603
604 pub fn column_count(&self) -> i32 {
606 self.table.cols()
607 }
608
609 pub fn col_width(&self, col: i32) -> i32 {
611 self.table.col_width(col)
612 }
613
614 pub fn row_height(&self, row: i32) -> i32 {
616 self.table.row_height(row)
617 }
618
619 pub fn set_col_width(&mut self, col: i32, width: i32) {
621 self.table.set_col_width(col, width);
622 }
623
624 pub fn set_row_height(&mut self, row: i32, height: i32) {
626 self.table.set_row_height(row, height);
627 }
628
629 pub fn col_header_height(&self) -> i32 {
631 self.table.col_header_height()
632 }
633
634 pub fn row_header_width(&self) -> i32 {
636 self.table.row_header_width()
637 }
638
639 pub fn set_col_header_height(&mut self, height: i32) {
641 self.table.set_col_header_height(height);
642 }
643
644 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);