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 = self.run_with_bound_context(|| 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        flags.validate_for_setup("Ui::table_setup_column_with_indent()", width, indent);
176        let init_width_or_weight = width.map_or(0.0, TableColumnWidth::value);
177        assert!(
178            init_width_or_weight.is_finite(),
179            "Ui::table_setup_column_with_indent() width or weight must be finite"
180        );
181        let label_ptr = self.scratch_txt(label);
182        let raw_flags = flags.bits()
183            | width.map_or(0, TableColumnWidth::raw_flags)
184            | indent.map_or(0, TableColumnIndent::raw_flags);
185        let user_id = optional_user_id_raw(user_id, "Ui::table_setup_column_with_indent()");
186        self.run_with_bound_context(|| {
187            let table = assert_current_table("Ui::table_setup_column_with_indent()");
188            assert!(
189                unsafe { i32::from((*table).DeclColumnsCount) < (*table).ColumnsCount },
190                "Ui::table_setup_column_with_indent() called more times than the table column count"
191            );
192            assert_table_setup_phase("Ui::table_setup_column_with_indent()");
193            unsafe {
194                sys::igTableSetupColumn(label_ptr, raw_flags, init_width_or_weight, user_id);
195            }
196        });
197    }
198
199    /// Setup a column with a fixed initial width.
200    pub fn table_setup_column_fixed_width(
201        &self,
202        label: impl AsRef<str>,
203        flags: TableColumnFlags,
204        width: f32,
205        user_id: Option<Id>,
206    ) {
207        self.table_setup_column(label, flags, Some(TableColumnWidth::Fixed(width)), user_id);
208    }
209
210    /// Setup a column with a stretch weight.
211    pub fn table_setup_column_stretch_weight(
212        &self,
213        label: impl AsRef<str>,
214        flags: TableColumnFlags,
215        weight: f32,
216        user_id: Option<Id>,
217    ) {
218        self.table_setup_column(
219            label,
220            flags,
221            Some(TableColumnWidth::Stretch(weight)),
222            user_id,
223        );
224    }
225
226    /// Submit all headers cells based on data provided to TableSetupColumn() + submit context menu
227    pub fn table_headers_row(&self) {
228        self.run_with_bound_context(|| {
229            assert_current_table("Ui::table_headers_row()");
230            unsafe {
231                sys::igTableHeadersRow();
232            }
233        });
234    }
235
236    /// Append into the next column (or first column of next row if currently in last column)
237    pub fn table_next_column(&self) -> bool {
238        self.run_with_bound_context(|| unsafe { sys::igTableNextColumn() })
239    }
240
241    /// Append into the specified column
242    pub fn table_set_column_index(&self, column: impl Into<TableColumnIndex>) -> bool {
243        let column = column.into();
244        let column_n = column.into_i32("Ui::table_set_column_index()");
245        self.run_with_bound_context(|| {
246            if let Some(table) = current_table_if_any() {
247                assert_valid_table_column_raw_in(table, column_n, "Ui::table_set_column_index()");
248            }
249            unsafe { sys::igTableSetColumnIndex(column_n) }
250        })
251    }
252
253    /// Append into the next row
254    pub fn table_next_row(&self) {
255        self.table_next_row_with_flags(TableRowFlags::NONE, 0.0);
256    }
257
258    /// Append into the next row with flags and minimum height
259    pub fn table_next_row_with_flags(&self, flags: TableRowFlags, min_row_height: f32) {
260        self.run_with_bound_context(|| unsafe {
261            sys::igTableNextRow(flags.bits(), min_row_height);
262        });
263    }
264
265    /// Freeze columns/rows so they stay visible when scrolling.
266    #[doc(alias = "TableSetupScrollFreeze")]
267    pub fn table_setup_scroll_freeze(&self, frozen_cols: usize, frozen_rows: usize) {
268        let frozen_cols = table_freeze_count_to_i32(
269            "Ui::table_setup_scroll_freeze()",
270            "frozen_cols",
271            frozen_cols,
272            TABLE_MAX_COLUMNS,
273        );
274        let frozen_rows = table_freeze_count_to_i32(
275            "Ui::table_setup_scroll_freeze()",
276            "frozen_rows",
277            frozen_rows,
278            128,
279        );
280        self.run_with_bound_context(|| {
281            assert_table_setup_phase("Ui::table_setup_scroll_freeze()");
282            unsafe { sys::igTableSetupScrollFreeze(frozen_cols, frozen_rows) }
283        });
284    }
285
286    /// Submit one header cell at current column position.
287    #[doc(alias = "TableHeader")]
288    pub fn table_header(&self, label: impl AsRef<str>) {
289        let label_ptr = self.scratch_txt(label);
290        self.run_with_bound_context(|| {
291            assert_current_table_cell("Ui::table_header()");
292            unsafe { sys::igTableHeader(label_ptr) }
293        });
294    }
295
296    /// Return columns count.
297    #[doc(alias = "TableGetColumnCount")]
298    pub fn table_get_column_count(&self) -> usize {
299        usize::try_from(self.run_with_bound_context(|| unsafe { sys::igTableGetColumnCount() }))
300            .expect("Dear ImGui returned a negative table column count")
301    }
302
303    /// Return current column index, or `None` when no table cell is current.
304    #[doc(alias = "TableGetColumnIndex")]
305    pub fn table_get_column_index(&self) -> Option<TableColumnIndex> {
306        self.run_with_bound_context(|| {
307            current_table_if_any()?;
308            let raw = unsafe { sys::igTableGetColumnIndex() };
309            (raw >= 0).then(|| TableColumnIndex::from_i32(raw, "Ui::table_get_column_index()"))
310        })
311    }
312
313    /// Return current row index, or `None` when no table row is current.
314    #[doc(alias = "TableGetRowIndex")]
315    pub fn table_get_row_index(&self) -> Option<TableRowIndex> {
316        self.run_with_bound_context(|| {
317            current_table_if_any()?;
318            let raw = unsafe { sys::igTableGetRowIndex() };
319            (raw >= 0).then(|| TableRowIndex::from_i32(raw, "Ui::table_get_row_index()"))
320        })
321    }
322
323    /// Return the name of a column by index.
324    #[doc(alias = "TableGetColumnName")]
325    pub fn table_get_column_name(&self, column: impl Into<TableColumnRef>) -> &str {
326        let column = column.into();
327        let column_n = match column {
328            TableColumnRef::Current => -1,
329            TableColumnRef::Index(index) => index.into_i32("Ui::table_get_column_name()"),
330        };
331        self.run_with_bound_context(|| {
332            if current_table_if_any().is_some() {
333                resolve_table_column(column, "Ui::table_get_column_name()");
334            }
335            unsafe {
336                let ptr = sys::igTableGetColumnName_Int(column_n);
337                if ptr.is_null() {
338                    ""
339                } else {
340                    CStr::from_ptr(ptr).to_str().unwrap_or("")
341                }
342            }
343        })
344    }
345
346    /// Return the flags of a column by index.
347    #[doc(alias = "TableGetColumnFlags")]
348    pub fn table_get_column_flags(
349        &self,
350        column: impl Into<TableColumnRef>,
351    ) -> TableColumnStateFlags {
352        let column = column.into();
353        let column_n = match column {
354            TableColumnRef::Current => -1,
355            TableColumnRef::Index(index) => index.into_i32("Ui::table_get_column_flags()"),
356        };
357        self.run_with_bound_context(|| {
358            if let Some(table) = current_table_if_any() {
359                let column_count = unsafe { (*table).ColumnsCount };
360                let resolved_column = match column {
361                    TableColumnRef::Current => unsafe { (*table).CurrentColumn },
362                    TableColumnRef::Index(_) => column_n,
363                };
364                assert!(
365                    (0..column_count).contains(&resolved_column),
366                    "Ui::table_get_column_flags() column index {resolved_column} is outside the current table column range 0..{column_count}"
367                );
368            }
369            unsafe { TableColumnStateFlags::from_bits_retain(sys::igTableGetColumnFlags(column_n)) }
370        })
371    }
372
373    /// Enable/disable a column by index.
374    #[doc(alias = "TableSetColumnEnabled")]
375    pub fn table_set_column_enabled(&self, column: impl Into<TableColumnRef>, enabled: bool) {
376        let column = column.into();
377        let column_n = match column {
378            TableColumnRef::Current => -1,
379            TableColumnRef::Index(index) => index.into_i32("Ui::table_set_column_enabled()"),
380        };
381        self.run_with_bound_context(|| {
382            assert_current_table_has_flags(TableFlags::HIDEABLE, "Ui::table_set_column_enabled()");
383            resolve_table_column(column, "Ui::table_set_column_enabled()");
384            unsafe { sys::igTableSetColumnEnabled(column_n, enabled) }
385        });
386    }
387
388    /// Return hovered column index, or -1 when none.
389    #[doc(alias = "TableGetHoveredColumn")]
390    pub fn table_get_hovered_column(&self) -> TableHoveredColumn {
391        self.run_with_bound_context(|| {
392            let raw = unsafe { sys::igTableGetHoveredColumn() };
393            if raw < 0 {
394                return TableHoveredColumn::None;
395            }
396            if let Some(table) = current_table_if_any() {
397                let column_count = unsafe { (*table).ColumnsCount };
398                if raw == column_count {
399                    return TableHoveredColumn::UnusedSpace;
400                }
401            }
402            TableHoveredColumn::Column(TableColumnIndex::from_i32(
403                raw,
404                "Ui::table_get_hovered_column()",
405            ))
406        })
407    }
408
409    /// Set column width (for fixed-width columns).
410    #[doc(alias = "TableSetColumnWidth")]
411    pub fn table_set_column_width(&self, column: impl Into<TableColumnIndex>, width: f32) {
412        assert_non_negative_finite_f32("Ui::table_set_column_width()", "width", width);
413        let column = column.into();
414        self.run_with_bound_context(|| {
415            assert_table_column_width_phase("Ui::table_set_column_width()");
416            let column_n = assert_valid_table_column(column, "Ui::table_set_column_width()");
417            unsafe { sys::igTableSetColumnWidth(column_n, width) }
418        });
419    }
420
421    /// Set a table background color target.
422    ///
423    /// Color must be an ImGui-packed ImU32 in ABGR order (IM_COL32).
424    /// Use `crate::colors::Color::to_imgui_u32()` to convert RGBA floats.
425    #[doc(alias = "TableSetBgColor")]
426    pub fn table_set_cell_bg_color_u32(&self, color: u32, column: impl Into<TableColumnRef>) {
427        let column = column.into();
428        let column_n = match column {
429            TableColumnRef::Current => -1,
430            TableColumnRef::Index(index) => index.into_i32("Ui::table_set_cell_bg_color_u32()"),
431        };
432        self.run_with_bound_context(|| {
433            assert_current_table_row("Ui::table_set_cell_bg_color_u32()");
434            resolve_table_column(column, "Ui::table_set_cell_bg_color_u32()");
435            unsafe { sys::igTableSetBgColor(TableBgTarget::CellBg as i32, color, column_n) }
436        });
437    }
438
439    /// Set a table cell background color using RGBA color (0..=1 floats).
440    pub fn table_set_cell_bg_color(&self, rgba: [f32; 4], column: impl Into<TableColumnRef>) {
441        let col = crate::colors::Color::from_array(rgba).to_imgui_u32();
442        self.table_set_cell_bg_color_u32(col, column);
443    }
444
445    /// Set the first row background color for the current table row.
446    #[doc(alias = "TableSetBgColor")]
447    pub fn table_set_row_bg0_color_u32(&self, color: u32) {
448        self.run_with_bound_context(|| {
449            assert_current_table_row("Ui::table_set_row_bg0_color_u32()");
450            unsafe { sys::igTableSetBgColor(TableBgTarget::RowBg0 as i32, color, -1) }
451        });
452    }
453
454    /// Set the first row background color using RGBA color (0..=1 floats).
455    pub fn table_set_row_bg0_color(&self, rgba: [f32; 4]) {
456        let col = crate::colors::Color::from_array(rgba).to_imgui_u32();
457        self.table_set_row_bg0_color_u32(col);
458    }
459
460    /// Set the second row background color for the current table row.
461    #[doc(alias = "TableSetBgColor")]
462    pub fn table_set_row_bg1_color_u32(&self, color: u32) {
463        self.run_with_bound_context(|| {
464            assert_current_table_row("Ui::table_set_row_bg1_color_u32()");
465            unsafe { sys::igTableSetBgColor(TableBgTarget::RowBg1 as i32, color, -1) }
466        });
467    }
468
469    /// Set the second row background color using RGBA color (0..=1 floats).
470    pub fn table_set_row_bg1_color(&self, rgba: [f32; 4]) {
471        let col = crate::colors::Color::from_array(rgba).to_imgui_u32();
472        self.table_set_row_bg1_color_u32(col);
473    }
474
475    /// Return hovered row from the previous frame.
476    #[doc(alias = "TableGetHoveredRow")]
477    pub fn table_get_hovered_row(&self) -> TableHoveredRow {
478        self.run_with_bound_context(|| {
479            if current_table_if_any().is_none() {
480                return TableHoveredRow::None;
481            }
482            let raw = unsafe { sys::igTableGetHoveredRow() };
483            if raw < 0 {
484                return TableHoveredRow::None;
485            }
486            TableHoveredRow::Row(TableRowIndex::from_i32(raw, "Ui::table_get_hovered_row()"))
487        })
488    }
489
490    /// Header row height in pixels.
491    #[doc(alias = "TableGetHeaderRowHeight")]
492    pub fn table_get_header_row_height(&self) -> f32 {
493        self.run_with_bound_context(|| unsafe { sys::igTableGetHeaderRowHeight() })
494    }
495
496    /// Set sort direction for a column. Optionally append to existing sort specs (multi-sort).
497    #[doc(alias = "TableSetColumnSortDirection")]
498    pub fn table_set_column_sort_direction(
499        &self,
500        column: impl Into<TableColumnIndex>,
501        dir: SortDirection,
502        append_to_sort_specs: bool,
503    ) {
504        let column = column.into();
505        self.run_with_bound_context(|| unsafe {
506            let column_n =
507                assert_valid_table_column(column, "Ui::table_set_column_sort_direction()");
508            sys::igTableSetColumnSortDirection(column_n, dir.into(), append_to_sort_specs)
509        });
510    }
511
512    /// Get current table sort specifications, if any.
513    /// When non-None and `is_dirty()` is true, the application should sort its data and
514    /// then call `clear_dirty()`.
515    #[doc(alias = "TableGetSortSpecs")]
516    pub fn table_get_sort_specs(&self) -> Option<TableSortSpecs<'_>> {
517        self.run_with_bound_context(|| unsafe {
518            let ptr = sys::igTableGetSortSpecs();
519            if ptr.is_null() {
520                None
521            } else {
522                Some(TableSortSpecs::from_raw(ptr))
523            }
524        })
525    }
526}