├── .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 | 
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 |
--------------------------------------------------------------------------------