Skip to main content

dear_imgui_rs/widget/table/
core.rs

1use crate::sys;
2use crate::ui::Ui;
3use crate::widget::table::{
4    SortDirection, TABLE_MAX_COLUMNS, TableBgTarget, TableBuilder, TableColumnFlags,
5    TableColumnIndent, TableColumnIndex, TableColumnRef, TableColumnSetup, TableColumnStateFlags,
6    TableColumnUserData, TableColumnWidth, TableFlags, TableHoveredColumn, TableHoveredRow,
7    TableOptions, TableRowFlags, TableRowIndex, TableSortSpecs, TableToken, assert_current_table,
8    assert_current_table_cell, assert_current_table_has_flags, assert_current_table_row,
9    assert_non_negative_finite_f32, assert_table_column_width_phase, assert_table_setup_phase,
10    assert_valid_table_column, assert_valid_table_column_raw_in, current_table_if_any,
11    resolve_table_column, table_column_count_to_i32, table_freeze_count_to_i32,
12};
13use std::borrow::Cow;
14use std::ffi::CStr;
15
16/// # Table Widgets
17impl Ui {
18    /// Start a Table builder for ergonomic setup + headers + options.
19    ///
20    /// Example
21    /// ```no_run
22    /// # use dear_imgui_rs::*;
23    /// # fn demo(ui: &Ui) {
24    /// ui.table("perf")
25    ///     .flags(TableFlags::RESIZABLE | TableFlags::SORTABLE)
26    ///     .outer_size([600.0, 240.0])
27    ///     .freeze(1, 1)
28    ///     .column("Name").width(140.0).done()
29    ///     .column("Value").weight(1.0).done()
30    ///     .headers(true)
31    ///     .build(|ui| {
32    ///         ui.table_next_row();
33    ///         ui.table_next_column(); ui.text("CPU");
34    ///         ui.table_next_column(); ui.text("Intel");
35    ///     });
36    /// # }
37    /// ```
38    pub fn table<'ui>(&'ui self, str_id: impl Into<Cow<'ui, str>>) -> TableBuilder<'ui> {
39        TableBuilder::new(self, str_id)
40    }
41    /// Begins a table with no flags and with standard sizing constraints.
42    ///
43    /// This does no work on styling the headers (the top row) -- see either
44    /// [begin_table_header](Self::begin_table_header) or the more complex
45    /// [table_setup_column](Self::table_setup_column).
46    #[must_use = "if return is dropped immediately, table is ended immediately."]
47    #[doc(alias = "BeginTable")]
48    pub fn begin_table(
49        &self,
50        str_id: impl AsRef<str>,
51        column_count: usize,
52    ) -> Option<TableToken<'_>> {
53        self.begin_table_with_flags(str_id, column_count, TableFlags::NONE)
54    }
55
56    /// Begins a table with flags and with standard sizing constraints.
57    #[must_use = "if return is dropped immediately, table is ended immediately."]
58    pub fn begin_table_with_flags(
59        &self,
60        str_id: impl AsRef<str>,
61        column_count: usize,
62        flags: impl Into<TableOptions>,
63    ) -> Option<TableToken<'_>> {
64        self.begin_table_with_sizing(str_id, column_count, flags, [0.0, 0.0], 0.0)
65    }
66
67    /// Begins a table with all flags and sizing constraints. This is the base method,
68    /// and gives users the most flexibility.
69    #[must_use = "if return is dropped immediately, table is ended immediately."]
70    pub fn begin_table_with_sizing(
71        &self,
72        str_id: impl AsRef<str>,
73        column_count: usize,
74        flags: impl Into<TableOptions>,
75        outer_size: impl Into<[f32; 2]>,
76        inner_width: f32,
77    ) -> Option<TableToken<'_>> {
78        let options = flags.into();
79        options.validate("Ui::begin_table_with_sizing()");
80        assert!(
81            inner_width.is_finite(),
82            "Ui::begin_table_with_sizing() inner_width must be finite"
83        );
84        assert!(
85            !options.flags.contains(TableFlags::SCROLL_X) || inner_width >= 0.0,
86            "Ui::begin_table_with_sizing() inner_width must be non-negative when SCROLL_X is enabled"
87        );
88        let outer_size = outer_size.into();
89        assert!(
90            outer_size[0].is_finite() && outer_size[1].is_finite(),
91            "Ui::begin_table_with_sizing() outer_size must contain finite values"
92        );
93        let str_id_ptr = self.scratch_txt(str_id);
94        let outer_size_vec: sys::ImVec2 = outer_size.into();
95        let column_count = table_column_count_to_i32(column_count);
96
97        let should_render = self.run_with_bound_context(|| unsafe {
98            sys::igBeginTable(
99                str_id_ptr,
100                column_count,
101                options.raw(),
102                outer_size_vec,
103                inner_width,
104            )
105        });
106
107        if should_render {
108            Some(TableToken::new(self))
109        } else {
110            None
111        }
112    }
113
114    /// Begins a table with no flags and with standard sizing constraints.
115    ///
116    /// Takes an array of table header information, the length of which determines
117    /// how many columns will be created.
118    #[must_use = "if return is dropped immediately, table is ended immediately."]
119    pub fn begin_table_header<Name: AsRef<str>, const N: usize>(
120        &self,
121        str_id: impl AsRef<str>,
122        column_data: [TableColumnSetup<Name>; N],
123    ) -> Option<TableToken<'_>> {
124        self.begin_table_header_with_flags(str_id, column_data, TableFlags::NONE)
125    }
126
127    /// Begins a table with flags and with standard sizing constraints.
128    ///
129    /// Takes an array of table header information, the length of which determines
130    /// how many columns will be created.
131    #[must_use = "if return is dropped immediately, table is ended immediately."]
132    pub fn begin_table_header_with_flags<Name: AsRef<str>, const N: usize>(
133        &self,
134        str_id: impl AsRef<str>,
135        column_data: [TableColumnSetup<Name>; N],
136        flags: impl Into<TableOptions>,
137    ) -> Option<TableToken<'_>> {
138        if let Some(token) = self.begin_table_with_flags(str_id, N, flags) {
139            // Setup columns
140            for column in &column_data {
141                self.table_setup_column_with_indent_and_user_data(
142                    &column.name,
143                    column.flags,
144                    column.width,
145                    column.indent,
146                    column.user_data,
147                );
148            }
149            self.table_headers_row();
150            Some(token)
151        } else {
152            None
153        }
154    }
155
156    /// Setup a column for the current table
157    #[doc(alias = "TableSetupColumn")]
158    pub fn table_setup_column(
159        &self,
160        label: impl AsRef<str>,
161        flags: TableColumnFlags,
162        width: Option<TableColumnWidth>,
163    ) {
164        self.table_setup_column_with_indent(label, flags, width, None);
165    }
166
167    /// Setup a column for the current table with opaque application data.
168    pub fn table_setup_column_with_user_data(
169        &self,
170        label: impl AsRef<str>,
171        flags: TableColumnFlags,
172        width: Option<TableColumnWidth>,
173        user_data: impl Into<TableColumnUserData>,
174    ) {
175        self.table_setup_column_with_indent_and_user_data(label, flags, width, None, user_data);
176    }
177
178    /// Setup a column for the current table, including explicit indent policy.
179    pub fn table_setup_column_with_indent(
180        &self,
181        label: impl AsRef<str>,
182        flags: TableColumnFlags,
183        width: Option<TableColumnWidth>,
184        indent: Option<TableColumnIndent>,
185    ) {
186        self.table_setup_column_with_indent_and_user_data(label, flags, width, indent, 0);
187    }
188
189    /// Setup a column with explicit indent policy and opaque application data.
190    pub fn table_setup_column_with_indent_and_user_data(
191        &self,
192        label: impl AsRef<str>,
193        flags: TableColumnFlags,
194        width: Option<TableColumnWidth>,
195        indent: Option<TableColumnIndent>,
196        user_data: impl Into<TableColumnUserData>,
197    ) {
198        flags.validate_for_setup(
199            "Ui::table_setup_column_with_indent_and_user_data()",
200            width,
201            indent,
202        );
203        let init_width_or_weight = width.map_or(0.0, TableColumnWidth::value);
204        assert!(
205            init_width_or_weight.is_finite(),
206            "Ui::table_setup_column_with_indent_and_user_data() width or weight must be finite"
207        );
208        let label_ptr = self.scratch_txt(label);
209        let raw_flags = flags.bits()
210            | width.map_or(0, TableColumnWidth::raw_flags)
211            | indent.map_or(0, TableColumnIndent::raw_flags);
212        let user_data = user_data.into().get();
213        self.run_with_bound_context(|| {
214            let table = assert_current_table("Ui::table_setup_column_with_indent_and_user_data()");
215            assert!(
216                unsafe { i32::from((*table).DeclColumnsCount) < (*table).ColumnsCount },
217                "Ui::table_setup_column_with_indent_and_user_data() called more times than the table column count"
218            );
219            assert_table_setup_phase("Ui::table_setup_column_with_indent_and_user_data()");
220            unsafe {
221                sys::igTableSetupColumn(label_ptr, raw_flags, init_width_or_weight, user_data);
222            }
223        });
224    }
225
226    /// Setup a column with a fixed initial width.
227    pub fn table_setup_column_fixed_width(
228        &self,
229        label: impl AsRef<str>,
230        flags: TableColumnFlags,
231        width: f32,
232    ) {
233        self.table_setup_column(label, flags, Some(TableColumnWidth::Fixed(width)));
234    }
235
236    /// Setup a column with a stretch weight.
237    pub fn table_setup_column_stretch_weight(
238        &self,
239        label: impl AsRef<str>,
240        flags: TableColumnFlags,
241        weight: f32,
242    ) {
243        self.table_setup_column(label, flags, Some(TableColumnWidth::Stretch(weight)));
244    }
245
246    /// Submit all headers cells based on data provided to TableSetupColumn() + submit context menu
247    #[doc(alias = "TableHeadersRow")]
248    pub fn table_headers_row(&self) {
249        self.run_with_bound_context(|| {
250            assert_current_table("Ui::table_headers_row()");
251            unsafe {
252                sys::igTableHeadersRow();
253            }
254        });
255    }
256
257    /// Append into the next column (or first column of next row if currently in last column)
258    #[doc(alias = "TableNextColumn")]
259    pub fn table_next_column(&self) -> bool {
260        self.run_with_bound_context(|| unsafe { sys::igTableNextColumn() })
261    }
262
263    /// Append into the specified column
264    #[doc(alias = "TableSetColumnIndex")]
265    pub fn table_set_column_index(&self, column: impl Into<TableColumnIndex>) -> bool {
266        let column = column.into();
267        let column_n = column.into_i32("Ui::table_set_column_index()");
268        self.run_with_bound_context(|| {
269            if let Some(table) = current_table_if_any() {
270                assert_valid_table_column_raw_in(table, column_n, "Ui::table_set_column_index()");
271            }
272            unsafe { sys::igTableSetColumnIndex(column_n) }
273        })
274    }
275
276    /// Append into the next row
277    #[doc(alias = "TableNextRow")]
278    pub fn table_next_row(&self) {
279        self.table_next_row_with_flags(TableRowFlags::NONE, 0.0);
280    }
281
282    /// Append into the next row with flags and minimum height
283    pub fn table_next_row_with_flags(&self, flags: TableRowFlags, min_row_height: f32) {
284        self.run_with_bound_context(|| unsafe {
285            sys::igTableNextRow(flags.bits(), min_row_height);
286        });
287    }
288
289    /// Freeze columns/rows so they stay visible when scrolling.
290    #[doc(alias = "TableSetupScrollFreeze")]
291    pub fn table_setup_scroll_freeze(&self, frozen_cols: usize, frozen_rows: usize) {
292        let frozen_cols = table_freeze_count_to_i32(
293            "Ui::table_setup_scroll_freeze()",
294            "frozen_cols",
295            frozen_cols,
296            TABLE_MAX_COLUMNS,
297        );
298        let frozen_rows = table_freeze_count_to_i32(
299            "Ui::table_setup_scroll_freeze()",
300            "frozen_rows",
301            frozen_rows,
302            128,
303        );
304        self.run_with_bound_context(|| {
305            assert_table_setup_phase("Ui::table_setup_scroll_freeze()");
306            unsafe { sys::igTableSetupScrollFreeze(frozen_cols, frozen_rows) }
307        });
308    }
309
310    /// Submit one header cell at current column position.
311    #[doc(alias = "TableHeader")]
312    pub fn table_header(&self, label: impl AsRef<str>) {
313        let label_ptr = self.scratch_txt(label);
314        self.run_with_bound_context(|| {
315            assert_current_table_cell("Ui::table_header()");
316            unsafe { sys::igTableHeader(label_ptr) }
317        });
318    }
319
320    /// Return columns count.
321    #[doc(alias = "TableGetColumnCount")]
322    pub fn table_get_column_count(&self) -> usize {
323        usize::try_from(self.run_with_bound_context(|| unsafe { sys::igTableGetColumnCount() }))
324            .expect("Dear ImGui returned a negative table column count")
325    }
326
327    /// Return current column index, or `None` when no table cell is current.
328    #[doc(alias = "TableGetColumnIndex")]
329    pub fn table_get_column_index(&self) -> Option<TableColumnIndex> {
330        self.run_with_bound_context(|| {
331            current_table_if_any()?;
332            let raw = unsafe { sys::igTableGetColumnIndex() };
333            (raw >= 0).then(|| TableColumnIndex::from_i32(raw, "Ui::table_get_column_index()"))
334        })
335    }
336
337    /// Return current row index, or `None` when no table row is current.
338    #[doc(alias = "TableGetRowIndex")]
339    pub fn table_get_row_index(&self) -> Option<TableRowIndex> {
340        self.run_with_bound_context(|| {
341            current_table_if_any()?;
342            let raw = unsafe { sys::igTableGetRowIndex() };
343            (raw >= 0).then(|| TableRowIndex::from_i32(raw, "Ui::table_get_row_index()"))
344        })
345    }
346
347    /// Return the name of a column by index.
348    #[doc(alias = "TableGetColumnName")]
349    pub fn table_get_column_name(&self, column: impl Into<TableColumnRef>) -> &str {
350        let column = column.into();
351        let column_n = match column {
352            TableColumnRef::Current => -1,
353            TableColumnRef::Index(index) => index.into_i32("Ui::table_get_column_name()"),
354        };
355        self.run_with_bound_context(|| {
356            if current_table_if_any().is_some() {
357                resolve_table_column(column, "Ui::table_get_column_name()");
358            }
359            unsafe {
360                let ptr = sys::igTableGetColumnName_Int(column_n);
361                if ptr.is_null() {
362                    ""
363                } else {
364                    CStr::from_ptr(ptr).to_str().unwrap_or("")
365                }
366            }
367        })
368    }
369
370    /// Return the flags of a column by index.
371    #[doc(alias = "TableGetColumnFlags")]
372    pub fn table_get_column_flags(
373        &self,
374        column: impl Into<TableColumnRef>,
375    ) -> TableColumnStateFlags {
376        let column = column.into();
377        let column_n = match column {
378            TableColumnRef::Current => -1,
379            TableColumnRef::Index(index) => index.into_i32("Ui::table_get_column_flags()"),
380        };
381        self.run_with_bound_context(|| {
382            if let Some(table) = current_table_if_any() {
383                let column_count = unsafe { (*table).ColumnsCount };
384                let resolved_column = match column {
385                    TableColumnRef::Current => unsafe { (*table).CurrentColumn },
386                    TableColumnRef::Index(_) => column_n,
387                };
388                assert!(
389                    (0..column_count).contains(&resolved_column),
390                    "Ui::table_get_column_flags() column index {resolved_column} is outside the current table column range 0..{column_count}"
391                );
392            }
393            unsafe { TableColumnStateFlags::from_bits_retain(sys::igTableGetColumnFlags(column_n)) }
394        })
395    }
396
397    /// Enable/disable a column by index.
398    #[doc(alias = "TableSetColumnEnabled")]
399    pub fn table_set_column_enabled(&self, column: impl Into<TableColumnRef>, enabled: bool) {
400        let column = column.into();
401        let column_n = match column {
402            TableColumnRef::Current => -1,
403            TableColumnRef::Index(index) => index.into_i32("Ui::table_set_column_enabled()"),
404        };
405        self.run_with_bound_context(|| {
406            assert_current_table_has_flags(TableFlags::HIDEABLE, "Ui::table_set_column_enabled()");
407            resolve_table_column(column, "Ui::table_set_column_enabled()");
408            unsafe { sys::igTableSetColumnEnabled(column_n, enabled) }
409        });
410    }
411
412    /// Return hovered column index, or -1 when none.
413    #[doc(alias = "TableGetHoveredColumn")]
414    pub fn table_get_hovered_column(&self) -> TableHoveredColumn {
415        self.run_with_bound_context(|| {
416            let raw = unsafe { sys::igTableGetHoveredColumn() };
417            if raw < 0 {
418                return TableHoveredColumn::None;
419            }
420            if let Some(table) = current_table_if_any() {
421                let column_count = unsafe { (*table).ColumnsCount };
422                if raw == column_count {
423                    return TableHoveredColumn::UnusedSpace;
424                }
425            }
426            TableHoveredColumn::Column(TableColumnIndex::from_i32(
427                raw,
428                "Ui::table_get_hovered_column()",
429            ))
430        })
431    }
432
433    /// Set column width (for fixed-width columns).
434    #[doc(alias = "TableSetColumnWidth")]
435    pub fn table_set_column_width(&self, column: impl Into<TableColumnIndex>, width: f32) {
436        assert_non_negative_finite_f32("Ui::table_set_column_width()", "width", width);
437        let column = column.into();
438        self.run_with_bound_context(|| {
439            assert_table_column_width_phase("Ui::table_set_column_width()");
440            let column_n = assert_valid_table_column(column, "Ui::table_set_column_width()");
441            unsafe { sys::igTableSetColumnWidth(column_n, width) }
442        });
443    }
444
445    /// Set a table background color target.
446    ///
447    /// Color must be an ImGui-packed ImU32 in ABGR order (IM_COL32).
448    /// Use `crate::colors::Color::to_imgui_u32()` to convert RGBA floats.
449    #[doc(alias = "TableSetBgColor")]
450    pub fn table_set_cell_bg_color_u32(&self, color: u32, column: impl Into<TableColumnRef>) {
451        let column = column.into();
452        let column_n = match column {
453            TableColumnRef::Current => -1,
454            TableColumnRef::Index(index) => index.into_i32("Ui::table_set_cell_bg_color_u32()"),
455        };
456        self.run_with_bound_context(|| {
457            assert_current_table_row("Ui::table_set_cell_bg_color_u32()");
458            resolve_table_column(column, "Ui::table_set_cell_bg_color_u32()");
459            unsafe { sys::igTableSetBgColor(TableBgTarget::CellBg as i32, color, column_n) }
460        });
461    }
462
463    /// Set a table cell background color using RGBA color (0..=1 floats).
464    pub fn table_set_cell_bg_color(&self, rgba: [f32; 4], column: impl Into<TableColumnRef>) {
465        let col = crate::colors::Color::from_array(rgba).to_imgui_u32();
466        self.table_set_cell_bg_color_u32(col, column);
467    }
468
469    /// Set the first row background color for the current table row.
470    #[doc(alias = "TableSetBgColor")]
471    pub fn table_set_row_bg0_color_u32(&self, color: u32) {
472        self.run_with_bound_context(|| {
473            assert_current_table_row("Ui::table_set_row_bg0_color_u32()");
474            unsafe { sys::igTableSetBgColor(TableBgTarget::RowBg0 as i32, color, -1) }
475        });
476    }
477
478    /// Set the first row background color using RGBA color (0..=1 floats).
479    pub fn table_set_row_bg0_color(&self, rgba: [f32; 4]) {
480        let col = crate::colors::Color::from_array(rgba).to_imgui_u32();
481        self.table_set_row_bg0_color_u32(col);
482    }
483
484    /// Set the second row background color for the current table row.
485    #[doc(alias = "TableSetBgColor")]
486    pub fn table_set_row_bg1_color_u32(&self, color: u32) {
487        self.run_with_bound_context(|| {
488            assert_current_table_row("Ui::table_set_row_bg1_color_u32()");
489            unsafe { sys::igTableSetBgColor(TableBgTarget::RowBg1 as i32, color, -1) }
490        });
491    }
492
493    /// Set the second row background color using RGBA color (0..=1 floats).
494    pub fn table_set_row_bg1_color(&self, rgba: [f32; 4]) {
495        let col = crate::colors::Color::from_array(rgba).to_imgui_u32();
496        self.table_set_row_bg1_color_u32(col);
497    }
498
499    /// Return hovered row from the previous frame.
500    #[doc(alias = "TableGetHoveredRow")]
501    pub fn table_get_hovered_row(&self) -> TableHoveredRow {
502        self.run_with_bound_context(|| {
503            if current_table_if_any().is_none() {
504                return TableHoveredRow::None;
505            }
506            let raw = unsafe { sys::igTableGetHoveredRow() };
507            if raw < 0 {
508                return TableHoveredRow::None;
509            }
510            TableHoveredRow::Row(TableRowIndex::from_i32(raw, "Ui::table_get_hovered_row()"))
511        })
512    }
513
514    /// Header row height in pixels.
515    #[doc(alias = "TableGetHeaderRowHeight")]
516    pub fn table_get_header_row_height(&self) -> f32 {
517        self.run_with_bound_context(|| unsafe { sys::igTableGetHeaderRowHeight() })
518    }
519
520    /// Set sort direction for a column. Optionally append to existing sort specs (multi-sort).
521    #[doc(alias = "TableSetColumnSortDirection")]
522    pub fn table_set_column_sort_direction(
523        &self,
524        column: impl Into<TableColumnIndex>,
525        dir: SortDirection,
526        append_to_sort_specs: bool,
527    ) {
528        let column = column.into();
529        self.run_with_bound_context(|| unsafe {
530            let column_n =
531                assert_valid_table_column(column, "Ui::table_set_column_sort_direction()");
532            sys::igTableSetColumnSortDirection(column_n, dir.into(), append_to_sort_specs)
533        });
534    }
535
536    /// Get current table sort specifications, if any.
537    /// When non-None and `is_dirty()` is true, the application should sort its data and
538    /// then call `clear_dirty()`.
539    #[doc(alias = "TableGetSortSpecs")]
540    pub fn table_get_sort_specs(&self) -> Option<TableSortSpecs<'_>> {
541        self.run_with_bound_context(|| unsafe {
542            let ptr = sys::igTableGetSortSpecs();
543            if ptr.is_null() {
544                None
545            } else {
546                Some(TableSortSpecs::from_raw(ptr))
547            }
548        })
549    }
550}