Skip to main content

dear_imgui_rs/widget/table/
core.rs

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