├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── headers.rs ├── int_table.rs ├── simple.rs └── styled.rs ├── screenshots └── styled.jpg ├── src └── lib.rs └── tests └── thread.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | ## [0.3.1] - 2023-07-29 5 | - Adds SmartTable::set_on_update_callback by @XPXPv2. 6 | 7 | ## [0.3.0] - 2023-04-01 8 | - Update to Rust edition 2021 and fltk-rs 1.4. 9 | 10 | ## [0.2.2] - 2023-03-18 11 | - Get and set width/height of rows/columns by @hwjsnc. 12 | - Determine column count using inner table, fixing issue #14, by @hwjsnc. 13 | 14 | ## [0.2.1] - 2022-04-09 15 | - Add cell_padding property. 16 | 17 | ## [0.2.0] - 2022-01-09 18 | - [BREAKING] Change input widget to `Option` 19 | - Adds clear(), row_count(), append_col(), insert_col(), append_row(), insert_row(). 20 | 21 | ## [0.1.8] - 2021-12-22 22 | - Add insert_empty_col() and insert_empty_row(). Thanks @peter-scholtens. 23 | - Add append_empty_col() and append_empty_row(). 24 | - Add a changelog. -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fltk-table" 3 | version = "0.3.4" 4 | authors = ["MoAlyousef "] 5 | edition = "2021" 6 | description = "A smart table widget for fltk-rs" 7 | repository = "https://github.com/fltk-rs/fltk-table" 8 | documentation = "https://docs.rs/fltk-table" 9 | keywords = ["gui", "ui", "widgets", "bindings", "graphics"] 10 | categories = ["gui"] 11 | readme = "README.md" 12 | license = "MIT" 13 | exclude = ["./examples"] 14 | 15 | [dependencies] 16 | fltk = "1.4" 17 | 18 | [[test]] 19 | name = "thread" 20 | path = "tests/thread.rs" 21 | harness = false -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fltk-table 2 | 3 | A smart table widget for fltk-rs. It aims to reduce the amount of boilerplate required to create a table. 4 | 5 | ## Usage 6 | ```toml 7 | [dependencies] 8 | fltk = "1.4" 9 | fltk-table = "0.3" 10 | ``` 11 | 12 | ## Example 13 | ```rust 14 | use fltk::{ 15 | app, enums, 16 | prelude::{GroupExt, WidgetExt}, 17 | window, 18 | }; 19 | use fltk_table::{SmartTable, TableOpts}; 20 | 21 | fn main() { 22 | let app = app::App::default().with_scheme(app::Scheme::Gtk); 23 | let mut wind = window::Window::default().with_size(800, 600); 24 | 25 | /// We pass the rows and columns thru the TableOpts field 26 | let mut table = SmartTable::default() 27 | .with_size(790, 590) 28 | .center_of_parent() 29 | .with_opts(TableOpts { 30 | rows: 30, 31 | cols: 15, 32 | editable: true, 33 | ..Default::default() 34 | }); 35 | 36 | wind.end(); 37 | wind.show(); 38 | 39 | // Just filling the vec with some values 40 | for i in 0..30 { 41 | for j in 0..15 { 42 | table.set_cell_value(i, j, &(i + j).to_string()); 43 | } 44 | } 45 | 46 | // set the value at the row,column 4,5 to "another", notice that indices start at 0 47 | table.set_cell_value(3, 4, "another"); 48 | 49 | assert_eq!(table.cell_value(3, 4), "another"); 50 | 51 | // To avoid closing the window on hitting the escape key 52 | wind.set_callback(move |_| { 53 | if app::event() == enums::Event::Close { 54 | app.quit(); 55 | } 56 | }); 57 | 58 | app.run().unwrap(); 59 | } 60 | ``` 61 | You can retrieve a copy of the data using the `SmartTable::data()` method. 62 | 63 | The TableOpts struct also takes styling elements for cells and headers: 64 | ```rust,ignore 65 | let mut table = SmartTable::default(TableOpts { 66 | rows: 30, 67 | cols: 15, 68 | cell_selection_color: Color::Red.inactive(), 69 | header_frame: FrameType::FlatBox, 70 | header_color: Color::BackGround.lighter(), 71 | cell_border_color: Color::White, 72 | ..Default::default() 73 | }); 74 | ``` 75 | 76 | ![image](screenshots/styled.jpg) 77 | 78 | The row/column header strings can also be changed using the `set_row_header_value()` and `set_col_header_value()` methods, which take an index to the required row/column. 79 | -------------------------------------------------------------------------------- /examples/headers.rs: -------------------------------------------------------------------------------- 1 | use fltk::{ 2 | app, 3 | prelude::{GroupExt, WidgetExt}, 4 | window, 5 | }; 6 | use fltk_table::{SmartTable, TableOpts}; 7 | 8 | fn main() { 9 | let app = app::App::default().with_scheme(app::Scheme::Gtk); 10 | let mut wind = window::Window::default().with_size(800, 600); 11 | 12 | let mut table = SmartTable::default() 13 | .with_size(790, 590) 14 | .center_of_parent() 15 | .with_opts(TableOpts { 16 | rows: 30, 17 | cols: 15, 18 | ..Default::default() 19 | }); 20 | 21 | wind.end(); 22 | wind.show(); 23 | 24 | for i in 0..30 { 25 | table.set_row_header_value(i, &(i + 100).to_string()); 26 | } 27 | 28 | for i in 0..15 { 29 | table.set_col_header_value(i, &i.to_string()); 30 | } 31 | 32 | app.run().unwrap(); 33 | } 34 | -------------------------------------------------------------------------------- /examples/int_table.rs: -------------------------------------------------------------------------------- 1 | use fltk::{ 2 | app, enums, input, 3 | prelude::{GroupExt, WidgetExt}, 4 | window, 5 | }; 6 | use fltk_table::{SmartTable, TableOpts}; 7 | 8 | fn main() { 9 | let app = app::App::default().with_scheme(app::Scheme::Gtk); 10 | let mut wind = window::Window::default().with_size(800, 600); 11 | 12 | // We pass the rows and columns thru the TableOpts field 13 | let mut table = SmartTable::default() 14 | .with_size(790, 590) 15 | .center_of_parent() 16 | .with_opts(TableOpts { 17 | rows: 30, 18 | cols: 1000, 19 | editable: true, 20 | ..Default::default() 21 | }); 22 | 23 | wind.end(); 24 | wind.show(); 25 | 26 | table 27 | .input() 28 | .as_mut() 29 | .unwrap() 30 | .set_type(input::InputType::Int); 31 | 32 | // set the value at the row,column 4,5 to "another", notice that indices start at 0 33 | table.set_cell_value(3, 4, "10"); 34 | assert_eq!(table.cell_value(3, 4), "10"); 35 | 36 | // To avoid closing the window on hitting the escape key 37 | wind.set_callback(move |_| { 38 | if app::event() == enums::Event::Close { 39 | app.quit(); 40 | } 41 | }); 42 | 43 | app.run().unwrap(); 44 | } 45 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | use fltk::{ 2 | app, enums, 3 | prelude::{GroupExt, WidgetExt}, 4 | window, 5 | }; 6 | use fltk_table::{SmartTable, TableOpts}; 7 | 8 | fn main() { 9 | let app = app::App::default().with_scheme(app::Scheme::Gtk); 10 | let mut wind = window::Window::default().with_size(800, 600); 11 | 12 | // We pass the rows and columns thru the TableOpts field 13 | let mut table = SmartTable::default() 14 | .with_size(790, 590) 15 | .center_of_parent() 16 | .with_opts(TableOpts { 17 | rows: 30, 18 | cols: 30, 19 | editable: true, 20 | ..Default::default() 21 | }); 22 | 23 | wind.end(); 24 | wind.show(); 25 | 26 | // set the value at the row,column 4,5 to "another", notice that indices start at 0 27 | table.set_cell_value(3, 4, "another"); 28 | assert_eq!(table.cell_value(3, 4), "another"); 29 | 30 | // To avoid closing the window on hitting the escape key 31 | wind.set_callback(move |_| { 32 | if app::event() == enums::Event::Close { 33 | app.quit(); 34 | } 35 | }); 36 | 37 | app.run().unwrap(); 38 | } 39 | -------------------------------------------------------------------------------- /examples/styled.rs: -------------------------------------------------------------------------------- 1 | use fltk::{ 2 | app, 3 | enums::*, 4 | prelude::{GroupExt, WidgetExt}, 5 | window, 6 | }; 7 | use fltk_table::{SmartTable, TableOpts}; 8 | 9 | fn main() { 10 | let app = app::App::default().with_scheme(app::Scheme::Gtk); 11 | let mut wind = window::Window::default().with_size(800, 600); 12 | 13 | let mut table = SmartTable::default() 14 | .with_size(790, 590) 15 | .center_of_parent() 16 | .with_opts(TableOpts { 17 | rows: 30, 18 | cols: 15, 19 | cell_selection_color: Color::Red.inactive(), 20 | header_frame: FrameType::FlatBox, 21 | header_color: Color::BackGround.lighter(), 22 | cell_border_color: Color::White, 23 | ..Default::default() 24 | }); 25 | 26 | wind.end(); 27 | wind.show(); 28 | 29 | // Just filling the vec with some values 30 | for i in 0..30 { 31 | for j in 0..15 { 32 | table.set_cell_value(i, j, &(i + j).to_string()); 33 | } 34 | } 35 | table.redraw(); 36 | 37 | // To avoid closing the window on hitting the escape key 38 | wind.set_callback(move |_| { 39 | if app::event() == Event::Close { 40 | app.quit(); 41 | } 42 | }); 43 | 44 | app.run().unwrap(); 45 | } 46 | -------------------------------------------------------------------------------- /screenshots/styled.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fltk-rs/fltk-table/e4a614de5ad518b8ce756beee4f118ba40d10c32/screenshots/styled.jpg -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![allow(clippy::needless_doctest_main)] 3 | 4 | use fltk::{ 5 | app, draw, 6 | enums::*, 7 | input, 8 | prelude::{GroupExt, InputExt, TableExt, WidgetBase, WidgetExt}, 9 | table, window, 10 | }; 11 | use std::cell::RefCell; 12 | use std::rc::Rc; 13 | use std::sync::{Arc, Mutex}; 14 | 15 | #[derive(Debug, Default, Clone)] 16 | pub struct Cell { 17 | label: String, 18 | color: Option, 19 | font: Option, 20 | font_color: Option, 21 | font_size: Option, 22 | selection_color: Option, 23 | align: Option, 24 | border_color: Option, 25 | } 26 | 27 | impl Cell { 28 | fn with_label(l: &str) -> Cell { 29 | Cell { 30 | label: l.to_string(), 31 | ..Default::default() 32 | } 33 | } 34 | } 35 | 36 | type CellMatrix = Vec>; 37 | 38 | // Needed to store cell information during the draw_cell call 39 | #[derive(Default)] 40 | struct 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 | 49 | impl 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)] 62 | pub 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 | 82 | impl 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)] 108 | pub struct SmartTable { 109 | table: table::TableRow, 110 | inp: Option, 111 | data: Arc>, 112 | row_headers: Arc>>, 113 | col_headers: Arc>>, 114 | on_update_callback: Arc>>, 115 | } 116 | 117 | impl Default for SmartTable { 118 | fn default() -> Self { 119 | Self::new(0, 0, 0, 0, None) 120 | } 121 | } 122 | 123 | impl SmartTable { 124 | /// Construct a new SmartTable widget using coords, size and label 125 | pub fn new>>(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 = 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 { 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> { 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(&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(&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 | 650 | impl 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 | 661 | fltk::widget_extends!(SmartTable, table::TableRow, table); 662 | -------------------------------------------------------------------------------- /tests/thread.rs: -------------------------------------------------------------------------------- 1 | use fltk::{ 2 | app, enums, 3 | prelude::{GroupExt, WidgetExt}, 4 | window, 5 | }; 6 | use fltk_table::{SmartTable, TableOpts}; 7 | 8 | fn main() { 9 | let app = app::App::default().with_scheme(app::Scheme::Gtk); 10 | let mut wind = window::Window::default().with_size(800, 600); 11 | 12 | let mut table = SmartTable::default() 13 | .with_size(790, 590) 14 | .center_of_parent() 15 | .with_opts(TableOpts { 16 | rows: 30, 17 | cols: 15, 18 | ..Default::default() 19 | }); 20 | 21 | wind.end(); 22 | wind.show(); 23 | 24 | std::thread::spawn({ 25 | let mut table = table.clone(); 26 | move || { 27 | app::sleep(0.1); 28 | loop { 29 | // Just filling the vec with some values 30 | for i in 0..30 { 31 | for j in 0..15 { 32 | table.set_cell_value(i, j, &(i + j).to_string()); 33 | app::sleep(0.03); 34 | app::awake(); 35 | table.redraw(); 36 | } 37 | } 38 | } 39 | } 40 | }); 41 | 42 | std::thread::spawn({ 43 | let mut table = table.clone(); 44 | move || { 45 | app::sleep(0.11); 46 | loop { 47 | // Just filling the vec with some values 48 | for i in 0..30 { 49 | for j in 0..15 { 50 | table.set_cell_value(0, 0, &(i + j).to_string()); 51 | app::sleep(0.01); 52 | app::awake(); 53 | table.redraw(); 54 | } 55 | } 56 | } 57 | } 58 | }); 59 | 60 | std::thread::spawn(move || { 61 | app::sleep(0.12); 62 | loop { 63 | // Just filling the vec with some values 64 | for i in 0..30 { 65 | for j in 0..15 { 66 | table.set_cell_value(0, 0, &(i + j).to_string()); 67 | app::sleep(0.01); 68 | app::awake(); 69 | table.redraw(); 70 | } 71 | } 72 | } 73 | }); 74 | 75 | // To avoid closing the window on hitting the escape key 76 | wind.set_callback(move |_| { 77 | if app::event() == enums::Event::Close { 78 | app.quit(); 79 | } 80 | }); 81 | 82 | app.run().unwrap(); 83 | } 84 | --------------------------------------------------------------------------------