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    ///
47    /// # Panics
48    ///
49    /// Panics if `column_count` is zero or reaches Dear ImGui's column limit.
50    #[must_use = "if return is dropped immediately, table is ended immediately."]
51    #[doc(alias = "BeginTable")]
52    pub fn begin_table(
53        &self,
54        str_id: impl AsRef<str>,
55        column_count: usize,
56    ) -> Option<TableToken<'_>> {
57        self.begin_table_with_flags(str_id, column_count, TableFlags::NONE)
58    }
59
60    /// Begins a table with flags and with standard sizing constraints.
61    ///
62    /// # Panics
63    ///
64    /// Panics for an invalid column count or incompatible table options.
65    #[must_use = "if return is dropped immediately, table is ended immediately."]
66    pub fn begin_table_with_flags(
67        &self,
68        str_id: impl AsRef<str>,
69        column_count: usize,
70        flags: impl Into<TableOptions>,
71    ) -> Option<TableToken<'_>> {
72        self.begin_table_with_sizing(str_id, column_count, flags, [0.0, 0.0], 0.0)
73    }
74
75    /// Begins a table with all flags and sizing constraints. This is the base method,
76    /// and gives users the most flexibility.
77    ///
78    /// # Panics
79    ///
80    /// Panics for an invalid column count or table option, non-finite sizing, a negative
81    /// `inner_width` when horizontal scrolling is enabled, or an active table draw-channel scope
82    /// on the current table cell.
83    #[must_use = "if return is dropped immediately, table is ended immediately."]
84    pub fn begin_table_with_sizing(
85        &self,
86        str_id: impl AsRef<str>,
87        column_count: usize,
88        flags: impl Into<TableOptions>,
89        outer_size: impl Into<[f32; 2]>,
90        inner_width: f32,
91    ) -> Option<TableToken<'_>> {
92        let options = flags.into();
93        options.validate("Ui::begin_table_with_sizing()");
94        assert!(
95            inner_width.is_finite(),
96            "Ui::begin_table_with_sizing() inner_width must be finite"
97        );
98        assert!(
99            !options.flags.contains(TableFlags::SCROLL_X) || inner_width >= 0.0,
100            "Ui::begin_table_with_sizing() inner_width must be non-negative when SCROLL_X is enabled"
101        );
102        let outer_size = outer_size.into();
103        assert!(
104            outer_size[0].is_finite() && outer_size[1].is_finite(),
105            "Ui::begin_table_with_sizing() outer_size must contain finite values"
106        );
107        let str_id_ptr = self.scratch_txt(str_id);
108        let outer_size_vec: sys::ImVec2 = outer_size.into();
109        let column_count = table_column_count_to_i32(column_count);
110
111        let should_render = self.run_with_bound_context(|| {
112            self.assert_no_active_table_channel("Ui::begin_table_with_sizing()");
113            unsafe {
114                sys::igBeginTable(
115                    str_id_ptr,
116                    column_count,
117                    options.raw(),
118                    outer_size_vec,
119                    inner_width,
120                )
121            }
122        });
123
124        if should_render {
125            Some(TableToken::new(self))
126        } else {
127            None
128        }
129    }
130
131    /// Begins a table with no flags and with standard sizing constraints.
132    ///
133    /// Takes an array of table header information, the length of which determines
134    /// how many columns will be created.
135    ///
136    /// # Panics
137    ///
138    /// Panics if the array is empty, reaches Dear ImGui's column limit, or contains invalid column
139    /// setup data.
140    #[must_use = "if return is dropped immediately, table is ended immediately."]
141    pub fn begin_table_header<Name: AsRef<str>, const N: usize>(
142        &self,
143        str_id: impl AsRef<str>,
144        column_data: [TableColumnSetup<Name>; N],
145    ) -> Option<TableToken<'_>> {
146        self.begin_table_header_with_flags(str_id, column_data, TableFlags::NONE)
147    }
148
149    /// Begins a table with flags and with standard sizing constraints.
150    ///
151    /// Takes an array of table header information, the length of which determines
152    /// how many columns will be created.
153    ///
154    /// # Panics
155    ///
156    /// Panics for an invalid column count, table option, or column setup value.
157    #[must_use = "if return is dropped immediately, table is ended immediately."]
158    pub fn begin_table_header_with_flags<Name: AsRef<str>, const N: usize>(
159        &self,
160        str_id: impl AsRef<str>,
161        column_data: [TableColumnSetup<Name>; N],
162        flags: impl Into<TableOptions>,
163    ) -> Option<TableToken<'_>> {
164        if let Some(token) = self.begin_table_with_flags(str_id, N, flags) {
165            // Setup columns
166            for column in &column_data {
167                self.table_setup_column_with_indent_and_user_data(
168                    &column.name,
169                    column.flags,
170                    column.width,
171                    column.indent,
172                    column.user_data,
173                );
174            }
175            self.table_headers_row();
176            Some(token)
177        } else {
178            None
179        }
180    }
181
182    /// Setup a column for the current table.
183    ///
184    /// # Panics
185    ///
186    /// Panics outside a table, after table layout has started, after all declared columns have
187    /// already been configured, or for an invalid flag/width combination or non-finite width.
188    #[doc(alias = "TableSetupColumn")]
189    pub fn table_setup_column(
190        &self,
191        label: impl AsRef<str>,
192        flags: TableColumnFlags,
193        width: Option<TableColumnWidth>,
194    ) {
195        self.table_setup_column_with_indent(label, flags, width, None);
196    }
197
198    /// Setup a column for the current table with opaque application data.
199    ///
200    /// # Panics
201    ///
202    /// Has the same validation and phase requirements as [`Ui::table_setup_column`].
203    pub fn table_setup_column_with_user_data(
204        &self,
205        label: impl AsRef<str>,
206        flags: TableColumnFlags,
207        width: Option<TableColumnWidth>,
208        user_data: impl Into<TableColumnUserData>,
209    ) {
210        self.table_setup_column_with_indent_and_user_data(label, flags, width, None, user_data);
211    }
212
213    /// Setup a column for the current table, including explicit indent policy.
214    ///
215    /// # Panics
216    ///
217    /// Has the same validation and phase requirements as [`Ui::table_setup_column`].
218    pub fn table_setup_column_with_indent(
219        &self,
220        label: impl AsRef<str>,
221        flags: TableColumnFlags,
222        width: Option<TableColumnWidth>,
223        indent: Option<TableColumnIndent>,
224    ) {
225        self.table_setup_column_with_indent_and_user_data(label, flags, width, indent, 0);
226    }
227
228    /// Setup a column with explicit indent policy and opaque application data.
229    ///
230    /// # Panics
231    ///
232    /// Panics outside a table, after table layout has started, after all declared columns have
233    /// already been configured, or for invalid flags, indent/width combinations, or non-finite
234    /// width/weight values.
235    pub fn table_setup_column_with_indent_and_user_data(
236        &self,
237        label: impl AsRef<str>,
238        flags: TableColumnFlags,
239        width: Option<TableColumnWidth>,
240        indent: Option<TableColumnIndent>,
241        user_data: impl Into<TableColumnUserData>,
242    ) {
243        flags.validate_for_setup(
244            "Ui::table_setup_column_with_indent_and_user_data()",
245            width,
246            indent,
247        );
248        let init_width_or_weight = width.map_or(0.0, TableColumnWidth::value);
249        assert!(
250            init_width_or_weight.is_finite(),
251            "Ui::table_setup_column_with_indent_and_user_data() width or weight must be finite"
252        );
253        let label_ptr = self.scratch_txt(label);
254        let raw_flags = flags.bits()
255            | width.map_or(0, TableColumnWidth::raw_flags)
256            | indent.map_or(0, TableColumnIndent::raw_flags);
257        let user_data = user_data.into().get();
258        self.run_with_bound_context(|| {
259            let table = assert_current_table("Ui::table_setup_column_with_indent_and_user_data()");
260            assert!(
261                unsafe { i32::from((*table).DeclColumnsCount) < (*table).ColumnsCount },
262                "Ui::table_setup_column_with_indent_and_user_data() called more times than the table column count"
263            );
264            assert_table_setup_phase("Ui::table_setup_column_with_indent_and_user_data()");
265            unsafe {
266                sys::igTableSetupColumn(label_ptr, raw_flags, init_width_or_weight, user_data);
267            }
268        });
269    }
270
271    /// Setup a column with a fixed initial width.
272    ///
273    /// # Panics
274    ///
275    /// Has the same validation and phase requirements as [`Ui::table_setup_column`].
276    pub fn table_setup_column_fixed_width(
277        &self,
278        label: impl AsRef<str>,
279        flags: TableColumnFlags,
280        width: f32,
281    ) {
282        self.table_setup_column(label, flags, Some(TableColumnWidth::Fixed(width)));
283    }
284
285    /// Setup a column with a stretch weight.
286    ///
287    /// # Panics
288    ///
289    /// Has the same validation and phase requirements as [`Ui::table_setup_column`].
290    pub fn table_setup_column_stretch_weight(
291        &self,
292        label: impl AsRef<str>,
293        flags: TableColumnFlags,
294        weight: f32,
295    ) {
296        self.table_setup_column(label, flags, Some(TableColumnWidth::Stretch(weight)));
297    }
298
299    /// Submit all header cells based on data provided to `TableSetupColumn()` and submit the
300    /// context-menu target.
301    ///
302    /// # Panics
303    ///
304    /// Panics outside a table or while a table draw-channel scope is active.
305    #[doc(alias = "TableHeadersRow")]
306    pub fn table_headers_row(&self) {
307        self.run_with_bound_context(|| {
308            assert_current_table("Ui::table_headers_row()");
309            self.assert_no_active_table_channel("Ui::table_headers_row()");
310            unsafe {
311                sys::igTableHeadersRow();
312            }
313        });
314    }
315
316    /// Append into the next column, or the first column of the next row when currently in the last
317    /// column.
318    ///
319    /// Returns `false` when no table is current.
320    ///
321    /// # Panics
322    ///
323    /// Panics while a table draw-channel scope is active.
324    #[doc(alias = "TableNextColumn")]
325    pub fn table_next_column(&self) -> bool {
326        self.run_with_bound_context(|| {
327            self.assert_no_active_table_channel("Ui::table_next_column()");
328            unsafe { sys::igTableNextColumn() }
329        })
330    }
331
332    /// Append into the specified column.
333    ///
334    /// Returns `false` when no table is current.
335    ///
336    /// # Panics
337    ///
338    /// Panics if `column` is outside the current table or while a table draw-channel scope is
339    /// active.
340    #[doc(alias = "TableSetColumnIndex")]
341    pub fn table_set_column_index(&self, column: impl Into<TableColumnIndex>) -> bool {
342        let column = column.into();
343        let column_n = column.into_i32("Ui::table_set_column_index()");
344        self.run_with_bound_context(|| {
345            self.assert_no_active_table_channel("Ui::table_set_column_index()");
346            if let Some(table) = current_table_if_any() {
347                assert_valid_table_column_raw_in(table, column_n, "Ui::table_set_column_index()");
348            }
349            unsafe { sys::igTableSetColumnIndex(column_n) }
350        })
351    }
352
353    /// Append into the next row.
354    ///
355    /// # Panics
356    ///
357    /// Panics outside a table or while a table draw-channel scope is active.
358    #[doc(alias = "TableNextRow")]
359    pub fn table_next_row(&self) {
360        self.table_next_row_with_flags(TableRowFlags::NONE, 0.0);
361    }
362
363    /// Append into the next row with flags and minimum height.
364    ///
365    /// # Panics
366    ///
367    /// Panics outside a table, while a table draw-channel scope is active, or when
368    /// `min_row_height` is negative or non-finite.
369    pub fn table_next_row_with_flags(&self, flags: TableRowFlags, min_row_height: f32) {
370        assert_non_negative_finite_f32(
371            "Ui::table_next_row_with_flags()",
372            "min_row_height",
373            min_row_height,
374        );
375        self.run_with_bound_context(|| {
376            assert_current_table("Ui::table_next_row_with_flags()");
377            self.assert_no_active_table_channel("Ui::table_next_row_with_flags()");
378            unsafe { sys::igTableNextRow(flags.bits(), min_row_height) };
379        });
380    }
381
382    /// Freeze columns/rows so they stay visible when scrolling.
383    ///
384    /// # Panics
385    ///
386    /// Panics outside a table, after the table setup phase, or when either freeze count exceeds
387    /// Dear ImGui's supported range.
388    #[doc(alias = "TableSetupScrollFreeze")]
389    pub fn table_setup_scroll_freeze(&self, frozen_cols: usize, frozen_rows: usize) {
390        let frozen_cols = table_freeze_count_to_i32(
391            "Ui::table_setup_scroll_freeze()",
392            "frozen_cols",
393            frozen_cols,
394            TABLE_MAX_COLUMNS,
395        );
396        let frozen_rows = table_freeze_count_to_i32(
397            "Ui::table_setup_scroll_freeze()",
398            "frozen_rows",
399            frozen_rows,
400            128,
401        );
402        self.run_with_bound_context(|| {
403            assert_table_setup_phase("Ui::table_setup_scroll_freeze()");
404            unsafe { sys::igTableSetupScrollFreeze(frozen_cols, frozen_rows) }
405        });
406    }
407
408    /// Submit one header cell at the current column position.
409    ///
410    /// # Panics
411    ///
412    /// Panics unless a table cell is current.
413    #[doc(alias = "TableHeader")]
414    pub fn table_header(&self, label: impl AsRef<str>) {
415        let label_ptr = self.scratch_txt(label);
416        self.run_with_bound_context(|| {
417            assert_current_table_cell("Ui::table_header()");
418            unsafe { sys::igTableHeader(label_ptr) }
419        });
420    }
421
422    /// Return the current table's column count, or zero when no table is current.
423    #[doc(alias = "TableGetColumnCount")]
424    pub fn table_get_column_count(&self) -> usize {
425        usize::try_from(self.run_with_bound_context(|| unsafe { sys::igTableGetColumnCount() }))
426            .expect("Dear ImGui returned a negative table column count")
427    }
428
429    /// Return current column index, or `None` when no table cell is current.
430    #[doc(alias = "TableGetColumnIndex")]
431    pub fn table_get_column_index(&self) -> Option<TableColumnIndex> {
432        self.run_with_bound_context(|| {
433            current_table_if_any()?;
434            let raw = unsafe { sys::igTableGetColumnIndex() };
435            (raw >= 0).then(|| TableColumnIndex::from_i32(raw, "Ui::table_get_column_index()"))
436        })
437    }
438
439    /// Return current row index, or `None` when no table row is current.
440    #[doc(alias = "TableGetRowIndex")]
441    pub fn table_get_row_index(&self) -> Option<TableRowIndex> {
442        self.run_with_bound_context(|| {
443            current_table_if_any()?;
444            let raw = unsafe { sys::igTableGetRowIndex() };
445            (raw >= 0).then(|| TableRowIndex::from_i32(raw, "Ui::table_get_row_index()"))
446        })
447    }
448
449    /// Return the name of a column by index.
450    ///
451    /// Returns an empty string when no table is current.
452    ///
453    /// # Panics
454    ///
455    /// Panics when a table is current and the requested/current column is invalid.
456    #[doc(alias = "TableGetColumnName")]
457    pub fn table_get_column_name(&self, column: impl Into<TableColumnRef>) -> &str {
458        let column = column.into();
459        let column_n = match column {
460            TableColumnRef::Current => -1,
461            TableColumnRef::Index(index) => index.into_i32("Ui::table_get_column_name()"),
462        };
463        self.run_with_bound_context(|| {
464            if current_table_if_any().is_some() {
465                resolve_table_column(column, "Ui::table_get_column_name()");
466            }
467            unsafe {
468                let ptr = sys::igTableGetColumnName_Int(column_n);
469                if ptr.is_null() {
470                    ""
471                } else {
472                    CStr::from_ptr(ptr).to_str().unwrap_or("")
473                }
474            }
475        })
476    }
477
478    /// Return the flags of a column by index.
479    ///
480    /// Returns empty flags when no table is current.
481    ///
482    /// # Panics
483    ///
484    /// Panics when a table is current and the requested/current column is invalid.
485    #[doc(alias = "TableGetColumnFlags")]
486    pub fn table_get_column_flags(
487        &self,
488        column: impl Into<TableColumnRef>,
489    ) -> TableColumnStateFlags {
490        let column = column.into();
491        let column_n = match column {
492            TableColumnRef::Current => -1,
493            TableColumnRef::Index(index) => index.into_i32("Ui::table_get_column_flags()"),
494        };
495        self.run_with_bound_context(|| {
496            if let Some(table) = current_table_if_any() {
497                let column_count = unsafe { (*table).ColumnsCount };
498                let resolved_column = match column {
499                    TableColumnRef::Current => unsafe { (*table).CurrentColumn },
500                    TableColumnRef::Index(_) => column_n,
501                };
502                assert!(
503                    (0..column_count).contains(&resolved_column),
504                    "Ui::table_get_column_flags() column index {resolved_column} is outside the current table column range 0..{column_count}"
505                );
506            }
507            unsafe { TableColumnStateFlags::from_bits_retain(sys::igTableGetColumnFlags(column_n)) }
508        })
509    }
510
511    /// Enable or disable a column by index.
512    ///
513    /// # Panics
514    ///
515    /// Panics outside a table, when the table lacks [`TableFlags::HIDEABLE`], or when the
516    /// requested/current column is invalid.
517    #[doc(alias = "TableSetColumnEnabled")]
518    pub fn table_set_column_enabled(&self, column: impl Into<TableColumnRef>, enabled: bool) {
519        let column = column.into();
520        let column_n = match column {
521            TableColumnRef::Current => -1,
522            TableColumnRef::Index(index) => index.into_i32("Ui::table_set_column_enabled()"),
523        };
524        self.run_with_bound_context(|| {
525            assert_current_table_has_flags(TableFlags::HIDEABLE, "Ui::table_set_column_enabled()");
526            resolve_table_column(column, "Ui::table_set_column_enabled()");
527            unsafe { sys::igTableSetColumnEnabled(column_n, enabled) }
528        });
529    }
530
531    /// Return the hovered column, unused table space, or [`TableHoveredColumn::None`] when no
532    /// table column is hovered.
533    #[doc(alias = "TableGetHoveredColumn")]
534    pub fn table_get_hovered_column(&self) -> TableHoveredColumn {
535        self.run_with_bound_context(|| {
536            let raw = unsafe { sys::igTableGetHoveredColumn() };
537            if raw < 0 {
538                return TableHoveredColumn::None;
539            }
540            if let Some(table) = current_table_if_any() {
541                let column_count = unsafe { (*table).ColumnsCount };
542                if raw == column_count {
543                    return TableHoveredColumn::UnusedSpace;
544                }
545            }
546            TableHoveredColumn::Column(TableColumnIndex::from_i32(
547                raw,
548                "Ui::table_get_hovered_column()",
549            ))
550        })
551    }
552
553    /// Set column width for a fixed-width column.
554    ///
555    /// # Panics
556    ///
557    /// Panics outside a table, after table layout is locked, before layout metrics are available,
558    /// for an invalid column, or when `width` is negative or non-finite.
559    #[doc(alias = "TableSetColumnWidth")]
560    pub fn table_set_column_width(&self, column: impl Into<TableColumnIndex>, width: f32) {
561        assert_non_negative_finite_f32("Ui::table_set_column_width()", "width", width);
562        let column = column.into();
563        self.run_with_bound_context(|| {
564            assert_table_column_width_phase("Ui::table_set_column_width()");
565            let column_n = assert_valid_table_column(column, "Ui::table_set_column_width()");
566            unsafe { sys::igTableSetColumnWidth(column_n, width) }
567        });
568    }
569
570    /// Set a table background color target.
571    ///
572    /// Color must be an ImGui-packed ImU32 in ABGR order (IM_COL32).
573    /// Use `crate::colors::Color::to_imgui_u32()` to convert RGBA floats.
574    ///
575    /// # Panics
576    ///
577    /// Panics unless a table row is current or when the requested/current column is invalid.
578    #[doc(alias = "TableSetBgColor")]
579    pub fn table_set_cell_bg_color_u32(&self, color: u32, column: impl Into<TableColumnRef>) {
580        let column = column.into();
581        let column_n = match column {
582            TableColumnRef::Current => -1,
583            TableColumnRef::Index(index) => index.into_i32("Ui::table_set_cell_bg_color_u32()"),
584        };
585        self.run_with_bound_context(|| {
586            assert_current_table_row("Ui::table_set_cell_bg_color_u32()");
587            resolve_table_column(column, "Ui::table_set_cell_bg_color_u32()");
588            unsafe { sys::igTableSetBgColor(TableBgTarget::CellBg as i32, color, column_n) }
589        });
590    }
591
592    /// Set a table cell background color using RGBA color (0..=1 floats).
593    ///
594    /// # Panics
595    ///
596    /// Has the same phase and column requirements as [`Ui::table_set_cell_bg_color_u32`].
597    pub fn table_set_cell_bg_color(&self, rgba: [f32; 4], column: impl Into<TableColumnRef>) {
598        let col = crate::colors::Color::from_array(rgba).to_imgui_u32();
599        self.table_set_cell_bg_color_u32(col, column);
600    }
601
602    /// Set the first row background color for the current table row.
603    ///
604    /// # Panics
605    ///
606    /// Panics unless a table row is current.
607    #[doc(alias = "TableSetBgColor")]
608    pub fn table_set_row_bg0_color_u32(&self, color: u32) {
609        self.run_with_bound_context(|| {
610            assert_current_table_row("Ui::table_set_row_bg0_color_u32()");
611            unsafe { sys::igTableSetBgColor(TableBgTarget::RowBg0 as i32, color, -1) }
612        });
613    }
614
615    /// Set the first row background color using RGBA color (0..=1 floats).
616    ///
617    /// # Panics
618    ///
619    /// Panics unless a table row is current.
620    pub fn table_set_row_bg0_color(&self, rgba: [f32; 4]) {
621        let col = crate::colors::Color::from_array(rgba).to_imgui_u32();
622        self.table_set_row_bg0_color_u32(col);
623    }
624
625    /// Set the second row background color for the current table row.
626    ///
627    /// # Panics
628    ///
629    /// Panics unless a table row is current.
630    #[doc(alias = "TableSetBgColor")]
631    pub fn table_set_row_bg1_color_u32(&self, color: u32) {
632        self.run_with_bound_context(|| {
633            assert_current_table_row("Ui::table_set_row_bg1_color_u32()");
634            unsafe { sys::igTableSetBgColor(TableBgTarget::RowBg1 as i32, color, -1) }
635        });
636    }
637
638    /// Set the second row background color using RGBA color (0..=1 floats).
639    ///
640    /// # Panics
641    ///
642    /// Panics unless a table row is current.
643    pub fn table_set_row_bg1_color(&self, rgba: [f32; 4]) {
644        let col = crate::colors::Color::from_array(rgba).to_imgui_u32();
645        self.table_set_row_bg1_color_u32(col);
646    }
647
648    /// Return hovered row from the previous frame.
649    #[doc(alias = "TableGetHoveredRow")]
650    pub fn table_get_hovered_row(&self) -> TableHoveredRow {
651        self.run_with_bound_context(|| {
652            if current_table_if_any().is_none() {
653                return TableHoveredRow::None;
654            }
655            let raw = unsafe { sys::igTableGetHoveredRow() };
656            if raw < 0 {
657                return TableHoveredRow::None;
658            }
659            TableHoveredRow::Row(TableRowIndex::from_i32(raw, "Ui::table_get_hovered_row()"))
660        })
661    }
662
663    /// Header row height in pixels.
664    ///
665    /// # Panics
666    ///
667    /// Panics outside a table.
668    #[doc(alias = "TableGetHeaderRowHeight")]
669    pub fn table_get_header_row_height(&self) -> f32 {
670        self.run_with_bound_context(|| {
671            assert_current_table("Ui::table_get_header_row_height()");
672            unsafe { sys::igTableGetHeaderRowHeight() }
673        })
674    }
675
676    /// Set sort direction for a column. Optionally append to existing sort specs (multi-sort).
677    ///
678    /// # Panics
679    ///
680    /// Panics outside a table, when the table lacks [`TableFlags::SORTABLE`], for an invalid
681    /// column, or when [`SortDirection::None`] is used without [`TableFlags::SORT_TRISTATE`].
682    #[doc(alias = "TableSetColumnSortDirection")]
683    pub fn table_set_column_sort_direction(
684        &self,
685        column: impl Into<TableColumnIndex>,
686        dir: SortDirection,
687        append_to_sort_specs: bool,
688    ) {
689        let column = column.into();
690        self.run_with_bound_context(|| {
691            let table = assert_current_table("Ui::table_set_column_sort_direction()");
692            let table_flags = TableFlags::from_bits_retain(unsafe { (*table).Flags });
693            assert!(
694                table_flags.contains(TableFlags::SORTABLE),
695                "Ui::table_set_column_sort_direction() requires the current table to have SORTABLE"
696            );
697            if dir == SortDirection::None {
698                assert!(
699                    table_flags.contains(TableFlags::SORT_TRISTATE),
700                    "Ui::table_set_column_sort_direction() requires SORT_TRISTATE for SortDirection::None"
701                );
702            }
703            let column_n =
704                assert_valid_table_column(column, "Ui::table_set_column_sort_direction()");
705            unsafe {
706                sys::igTableSetColumnSortDirection(column_n, dir.into(), append_to_sort_specs)
707            }
708        });
709    }
710
711    /// Get current table sort specifications, if any.
712    /// When non-None and `is_dirty()` is true, the application should sort its data and
713    /// then call [`TableSortSpecs::clear_dirty`] while this table is still current. Returns `None`
714    /// outside a table or when the table has no sort specifications. On a sortable table, this may
715    /// lock table layout, so finish all setup calls first.
716    #[doc(alias = "TableGetSortSpecs")]
717    pub fn table_get_sort_specs(&self) -> Option<TableSortSpecs> {
718        self.run_with_bound_context(|| unsafe {
719            let table = current_table_if_any()?;
720            let ptr = sys::igTableGetSortSpecs();
721            if ptr.is_null() {
722                None
723            } else {
724                Some(TableSortSpecs::from_raw(self, table, ptr))
725            }
726        })
727    }
728}