Skip to main content

dear_imgui_rs/widget/
table.rs

1//! Tables
2//!
3//! Modern multi-column layout and data table API with rich configuration.
4//! Use `TableBuilder` to declare columns and build rows/cells ergonomically.
5//!
6//! Quick example (builder):
7//! ```no_run
8//! # use dear_imgui_rs::*;
9//! # let mut ctx = Context::create();
10//! # let ui = ctx.frame();
11//! ui.table("perf")
12//!     .flags(TableFlags::RESIZABLE)
13//!     .column("Name").width(120.0).done()
14//!     .column("Value").weight(1.0).done()
15//!     .headers(true)
16//!     .build(|ui| {
17//!         ui.table_next_row();
18//!         ui.table_next_column(); ui.text("CPU");
19//!         ui.table_next_column(); ui.text("Intel");
20//!     });
21//! ```
22//!
23//! Quick example (manual API):
24//! ```no_run
25//! # use dear_imgui_rs::*;
26//! # let mut ctx = Context::create();
27//! # let ui = ctx.frame();
28//! if let Some(_t) = ui.begin_table("t", 2) {
29//!     ui.table_next_row();
30//!     ui.table_next_column(); ui.text("A");
31//!     ui.table_next_column(); ui.text("B");
32//! }
33//! ```
34//!
35#![allow(
36    clippy::cast_possible_truncation,
37    clippy::cast_sign_loss,
38    clippy::as_conversions
39)]
40use crate::draw::ImColor32;
41use crate::sys;
42use crate::ui::Ui;
43use crate::widget::{
44    TableColumnFlags, TableColumnIndent, TableColumnStateFlags, TableColumnWidth, TableFlags,
45    TableOptions, TableSizingPolicy,
46};
47#[cfg(feature = "serde")]
48use serde::{Deserialize, Serialize};
49use std::borrow::Cow;
50use std::ffi::CStr;
51
52/// Table column setup information
53#[derive(Clone, Debug)]
54pub struct TableColumnSetup<Name> {
55    pub name: Name,
56    pub flags: TableColumnFlags,
57    pub width: Option<TableColumnWidth>,
58    pub indent: Option<TableColumnIndent>,
59    pub user_id: u32,
60}
61
62impl<Name> TableColumnSetup<Name> {
63    /// Creates a new table column setup
64    pub fn new(name: Name) -> Self {
65        Self {
66            name,
67            flags: TableColumnFlags::NONE,
68            width: None,
69            indent: None,
70            user_id: 0,
71        }
72    }
73
74    /// Sets the column flags
75    pub fn flags(mut self, flags: TableColumnFlags) -> Self {
76        self.flags = flags;
77        self
78    }
79
80    /// Sets a fixed initial column width in pixels.
81    pub fn fixed_width(mut self, width: f32) -> Self {
82        self.width = Some(TableColumnWidth::Fixed(width));
83        self
84    }
85
86    /// Sets an initial stretch weight for this column.
87    pub fn stretch_weight(mut self, weight: f32) -> Self {
88        self.width = Some(TableColumnWidth::Stretch(weight));
89        self
90    }
91
92    /// Sets this column's indentation policy.
93    pub fn indent(mut self, indent: TableColumnIndent) -> Self {
94        self.indent = Some(indent);
95        self
96    }
97
98    /// Enables or disables indentation for this column.
99    pub fn indent_enabled(mut self, enabled: bool) -> Self {
100        self.indent = Some(if enabled {
101            TableColumnIndent::Enable
102        } else {
103            TableColumnIndent::Disable
104        });
105        self
106    }
107
108    /// Sets the user ID
109    pub fn user_id(mut self, id: u32) -> Self {
110        self.user_id = id;
111        self
112    }
113}
114
115/// # Table Widgets
116impl Ui {
117    /// Start a Table builder for ergonomic setup + headers + options.
118    ///
119    /// Example
120    /// ```no_run
121    /// # use dear_imgui_rs::*;
122    /// # fn demo(ui: &Ui) {
123    /// ui.table("perf")
124    ///     .flags(TableFlags::RESIZABLE | TableFlags::SORTABLE)
125    ///     .outer_size([600.0, 240.0])
126    ///     .freeze(1, 1)
127    ///     .column("Name").width(140.0).done()
128    ///     .column("Value").weight(1.0).done()
129    ///     .headers(true)
130    ///     .build(|ui| {
131    ///         ui.table_next_row();
132    ///         ui.table_next_column(); ui.text("CPU");
133    ///         ui.table_next_column(); ui.text("Intel");
134    ///     });
135    /// # }
136    /// ```
137    pub fn table<'ui>(&'ui self, str_id: impl Into<Cow<'ui, str>>) -> TableBuilder<'ui> {
138        TableBuilder::new(self, str_id)
139    }
140    /// Begins a table with no flags and with standard sizing constraints.
141    ///
142    /// This does no work on styling the headers (the top row) -- see either
143    /// [begin_table_header](Self::begin_table_header) or the more complex
144    /// [table_setup_column](Self::table_setup_column).
145    #[must_use = "if return is dropped immediately, table is ended immediately."]
146    pub fn begin_table(
147        &self,
148        str_id: impl AsRef<str>,
149        column_count: usize,
150    ) -> Option<TableToken<'_>> {
151        self.begin_table_with_flags(str_id, column_count, TableFlags::NONE)
152    }
153
154    /// Begins a table with flags and with standard sizing constraints.
155    #[must_use = "if return is dropped immediately, table is ended immediately."]
156    pub fn begin_table_with_flags(
157        &self,
158        str_id: impl AsRef<str>,
159        column_count: usize,
160        flags: impl Into<TableOptions>,
161    ) -> Option<TableToken<'_>> {
162        self.begin_table_with_sizing(str_id, column_count, flags, [0.0, 0.0], 0.0)
163    }
164
165    /// Begins a table with all flags and sizing constraints. This is the base method,
166    /// and gives users the most flexibility.
167    #[must_use = "if return is dropped immediately, table is ended immediately."]
168    pub fn begin_table_with_sizing(
169        &self,
170        str_id: impl AsRef<str>,
171        column_count: usize,
172        flags: impl Into<TableOptions>,
173        outer_size: impl Into<[f32; 2]>,
174        inner_width: f32,
175    ) -> Option<TableToken<'_>> {
176        let options = flags.into();
177        let str_id_ptr = self.scratch_txt(str_id);
178        let outer_size_vec: sys::ImVec2 = outer_size.into().into();
179
180        let should_render = unsafe {
181            sys::igBeginTable(
182                str_id_ptr,
183                column_count as i32,
184                options.raw(),
185                outer_size_vec,
186                inner_width,
187            )
188        };
189
190        if should_render {
191            Some(TableToken::new(self))
192        } else {
193            None
194        }
195    }
196
197    /// Begins a table with no flags and with standard sizing constraints.
198    ///
199    /// Takes an array of table header information, the length of which determines
200    /// how many columns will be created.
201    #[must_use = "if return is dropped immediately, table is ended immediately."]
202    pub fn begin_table_header<Name: AsRef<str>, const N: usize>(
203        &self,
204        str_id: impl AsRef<str>,
205        column_data: [TableColumnSetup<Name>; N],
206    ) -> Option<TableToken<'_>> {
207        self.begin_table_header_with_flags(str_id, column_data, TableFlags::NONE)
208    }
209
210    /// Begins a table with flags and with standard sizing constraints.
211    ///
212    /// Takes an array of table header information, the length of which determines
213    /// how many columns will be created.
214    #[must_use = "if return is dropped immediately, table is ended immediately."]
215    pub fn begin_table_header_with_flags<Name: AsRef<str>, const N: usize>(
216        &self,
217        str_id: impl AsRef<str>,
218        column_data: [TableColumnSetup<Name>; N],
219        flags: impl Into<TableOptions>,
220    ) -> Option<TableToken<'_>> {
221        if let Some(token) = self.begin_table_with_flags(str_id, N, flags) {
222            // Setup columns
223            for column in &column_data {
224                self.table_setup_column_with_indent(
225                    &column.name,
226                    column.flags,
227                    column.width,
228                    column.indent,
229                    column.user_id,
230                );
231            }
232            self.table_headers_row();
233            Some(token)
234        } else {
235            None
236        }
237    }
238
239    /// Setup a column for the current table
240    pub fn table_setup_column(
241        &self,
242        label: impl AsRef<str>,
243        flags: TableColumnFlags,
244        width: Option<TableColumnWidth>,
245        user_id: u32,
246    ) {
247        self.table_setup_column_with_indent(label, flags, width, None, user_id);
248    }
249
250    /// Setup a column for the current table, including explicit indent policy.
251    pub fn table_setup_column_with_indent(
252        &self,
253        label: impl AsRef<str>,
254        flags: TableColumnFlags,
255        width: Option<TableColumnWidth>,
256        indent: Option<TableColumnIndent>,
257        user_id: u32,
258    ) {
259        let label_ptr = self.scratch_txt(label);
260        let raw_flags = flags.bits()
261            | width.map_or(0, TableColumnWidth::raw_flags)
262            | indent.map_or(0, TableColumnIndent::raw_flags);
263        let init_width_or_weight = width.map_or(0.0, TableColumnWidth::value);
264        unsafe {
265            sys::igTableSetupColumn(label_ptr, raw_flags, init_width_or_weight, user_id);
266        }
267    }
268
269    /// Setup a column with a fixed initial width.
270    pub fn table_setup_column_fixed_width(
271        &self,
272        label: impl AsRef<str>,
273        flags: TableColumnFlags,
274        width: f32,
275        user_id: u32,
276    ) {
277        self.table_setup_column(label, flags, Some(TableColumnWidth::Fixed(width)), user_id);
278    }
279
280    /// Setup a column with a stretch weight.
281    pub fn table_setup_column_stretch_weight(
282        &self,
283        label: impl AsRef<str>,
284        flags: TableColumnFlags,
285        weight: f32,
286        user_id: u32,
287    ) {
288        self.table_setup_column(
289            label,
290            flags,
291            Some(TableColumnWidth::Stretch(weight)),
292            user_id,
293        );
294    }
295
296    /// Submit all headers cells based on data provided to TableSetupColumn() + submit context menu
297    pub fn table_headers_row(&self) {
298        unsafe {
299            sys::igTableHeadersRow();
300        }
301    }
302
303    /// Append into the next column (or first column of next row if currently in last column)
304    pub fn table_next_column(&self) -> bool {
305        unsafe { sys::igTableNextColumn() }
306    }
307
308    /// Append into the specified column
309    pub fn table_set_column_index(&self, column_n: i32) -> bool {
310        unsafe { sys::igTableSetColumnIndex(column_n) }
311    }
312
313    /// Append into the next row
314    pub fn table_next_row(&self) {
315        self.table_next_row_with_flags(TableRowFlags::NONE, 0.0);
316    }
317
318    /// Append into the next row with flags and minimum height
319    pub fn table_next_row_with_flags(&self, flags: TableRowFlags, min_row_height: f32) {
320        unsafe {
321            sys::igTableNextRow(flags.bits(), min_row_height);
322        }
323    }
324
325    /// Freeze columns/rows so they stay visible when scrolling.
326    #[doc(alias = "TableSetupScrollFreeze")]
327    pub fn table_setup_scroll_freeze(&self, frozen_cols: i32, frozen_rows: i32) {
328        unsafe { sys::igTableSetupScrollFreeze(frozen_cols, frozen_rows) }
329    }
330
331    /// Submit one header cell at current column position.
332    #[doc(alias = "TableHeader")]
333    pub fn table_header(&self, label: impl AsRef<str>) {
334        let label_ptr = self.scratch_txt(label);
335        unsafe { sys::igTableHeader(label_ptr) }
336    }
337
338    /// Return columns count.
339    #[doc(alias = "TableGetColumnCount")]
340    pub fn table_get_column_count(&self) -> i32 {
341        unsafe { sys::igTableGetColumnCount() }
342    }
343
344    /// Return current column index.
345    #[doc(alias = "TableGetColumnIndex")]
346    pub fn table_get_column_index(&self) -> i32 {
347        unsafe { sys::igTableGetColumnIndex() }
348    }
349
350    /// Return current row index.
351    #[doc(alias = "TableGetRowIndex")]
352    pub fn table_get_row_index(&self) -> i32 {
353        unsafe { sys::igTableGetRowIndex() }
354    }
355
356    /// Return the name of a column by index.
357    #[doc(alias = "TableGetColumnName")]
358    pub fn table_get_column_name(&self, column_n: i32) -> &str {
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    /// Return the flags of a column by index.
370    #[doc(alias = "TableGetColumnFlags")]
371    pub fn table_get_column_flags(&self, column_n: i32) -> TableColumnStateFlags {
372        unsafe { TableColumnStateFlags::from_bits_truncate(sys::igTableGetColumnFlags(column_n)) }
373    }
374
375    /// Enable/disable a column by index.
376    #[doc(alias = "TableSetColumnEnabled")]
377    pub fn table_set_column_enabled(&self, column_n: i32, enabled: bool) {
378        unsafe { sys::igTableSetColumnEnabled(column_n, enabled) }
379    }
380
381    /// Return hovered column index, or -1 when none.
382    #[doc(alias = "TableGetHoveredColumn")]
383    pub fn table_get_hovered_column(&self) -> i32 {
384        unsafe { sys::igTableGetHoveredColumn() }
385    }
386
387    /// Set column width (for fixed-width columns).
388    #[doc(alias = "TableSetColumnWidth")]
389    pub fn table_set_column_width(&self, column_n: i32, width: f32) {
390        unsafe { sys::igTableSetColumnWidth(column_n, width) }
391    }
392
393    /// Set a table background color target.
394    ///
395    /// Color must be an ImGui-packed ImU32 in ABGR order (IM_COL32).
396    /// Use `crate::colors::Color::to_imgui_u32()` to convert RGBA floats.
397    #[doc(alias = "TableSetBgColor")]
398    pub fn table_set_bg_color_u32(&self, target: TableBgTarget, color: u32, column_n: i32) {
399        unsafe { sys::igTableSetBgColor(target as i32, color, column_n) }
400    }
401
402    /// Set a table background color target using RGBA color (0..=1 floats).
403    pub fn table_set_bg_color(&self, target: TableBgTarget, rgba: [f32; 4], column_n: i32) {
404        // Pack to ImGui's ABGR layout.
405        let col = crate::colors::Color::from_array(rgba).to_imgui_u32();
406        unsafe { sys::igTableSetBgColor(target as i32, col, column_n) }
407    }
408
409    /// Return hovered row index, or -1 when none.
410    #[doc(alias = "TableGetHoveredRow")]
411    pub fn table_get_hovered_row(&self) -> i32 {
412        unsafe { sys::igTableGetHoveredRow() }
413    }
414
415    /// Header row height in pixels.
416    #[doc(alias = "TableGetHeaderRowHeight")]
417    pub fn table_get_header_row_height(&self) -> f32 {
418        unsafe { sys::igTableGetHeaderRowHeight() }
419    }
420
421    /// Set sort direction for a column. Optionally append to existing sort specs (multi-sort).
422    #[doc(alias = "TableSetColumnSortDirection")]
423    pub fn table_set_column_sort_direction(
424        &self,
425        column_n: i32,
426        dir: SortDirection,
427        append_to_sort_specs: bool,
428    ) {
429        unsafe { sys::igTableSetColumnSortDirection(column_n, dir.into(), append_to_sort_specs) }
430    }
431
432    /// Get current table sort specifications, if any.
433    /// When non-None and `is_dirty()` is true, the application should sort its data and
434    /// then call `clear_dirty()`.
435    #[doc(alias = "TableGetSortSpecs")]
436    pub fn table_get_sort_specs(&self) -> Option<TableSortSpecs<'_>> {
437        unsafe {
438            let ptr = sys::igTableGetSortSpecs();
439            if ptr.is_null() {
440                None
441            } else {
442                Some(TableSortSpecs::from_raw(ptr))
443            }
444        }
445    }
446}
447
448bitflags::bitflags! {
449    /// Flags for table rows
450    #[repr(transparent)]
451    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
452    pub struct TableRowFlags: i32 {
453        /// No flags
454        const NONE = 0;
455        /// Identify header row (set default background color + width of all columns)
456        const HEADERS = sys::ImGuiTableRowFlags_Headers as i32;
457    }
458}
459
460#[cfg(feature = "serde")]
461impl Serialize for TableRowFlags {
462    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
463    where
464        S: serde::Serializer,
465    {
466        serializer.serialize_i32(self.bits())
467    }
468}
469
470#[cfg(feature = "serde")]
471impl<'de> Deserialize<'de> for TableRowFlags {
472    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
473    where
474        D: serde::Deserializer<'de>,
475    {
476        let bits = i32::deserialize(deserializer)?;
477        Ok(TableRowFlags::from_bits_truncate(bits))
478    }
479}
480
481/// Target for table background colors.
482#[repr(i32)]
483#[derive(Copy, Clone, Debug, PartialEq, Eq)]
484#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
485pub enum TableBgTarget {
486    /// No background target
487    None = sys::ImGuiTableBgTarget_None as i32,
488    /// First alternating row background
489    RowBg0 = sys::ImGuiTableBgTarget_RowBg0 as i32,
490    /// Second alternating row background
491    RowBg1 = sys::ImGuiTableBgTarget_RowBg1 as i32,
492    /// Cell background
493    CellBg = sys::ImGuiTableBgTarget_CellBg as i32,
494}
495
496/// Sorting direction for table columns.
497#[repr(u8)]
498#[derive(Copy, Clone, Debug, PartialEq, Eq)]
499#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
500pub enum SortDirection {
501    None = sys::ImGuiSortDirection_None as u8,
502    Ascending = sys::ImGuiSortDirection_Ascending as u8,
503    Descending = sys::ImGuiSortDirection_Descending as u8,
504}
505
506impl From<SortDirection> for sys::ImGuiSortDirection {
507    #[inline]
508    fn from(value: SortDirection) -> sys::ImGuiSortDirection {
509        match value {
510            SortDirection::None => sys::ImGuiSortDirection_None,
511            SortDirection::Ascending => sys::ImGuiSortDirection_Ascending,
512            SortDirection::Descending => sys::ImGuiSortDirection_Descending,
513        }
514    }
515}
516
517/// One column sort spec.
518#[derive(Copy, Clone, Debug)]
519pub struct TableColumnSortSpec {
520    pub column_user_id: u32,
521    pub column_index: i16,
522    pub sort_order: i16,
523    pub sort_direction: SortDirection,
524}
525
526/// Table sort specs view.
527pub struct TableSortSpecs<'a> {
528    raw: *mut sys::ImGuiTableSortSpecs,
529    _marker: std::marker::PhantomData<&'a Ui>,
530}
531
532impl<'a> TableSortSpecs<'a> {
533    /// # Safety
534    /// `raw` must be a valid pointer returned by ImGui_TableGetSortSpecs for the current table.
535    pub(crate) unsafe fn from_raw(raw: *mut sys::ImGuiTableSortSpecs) -> Self {
536        Self {
537            raw,
538            _marker: std::marker::PhantomData,
539        }
540    }
541
542    /// Whether the specs are marked dirty by dear imgui (you should resort your data).
543    pub fn is_dirty(&self) -> bool {
544        unsafe { (*self.raw).SpecsDirty }
545    }
546
547    /// Clear the dirty flag after you've applied sorting to your data.
548    pub fn clear_dirty(&mut self) {
549        unsafe { (*self.raw).SpecsDirty = false }
550    }
551
552    /// Number of column specs.
553    pub fn len(&self) -> usize {
554        unsafe { (*self.raw).SpecsCount as usize }
555    }
556
557    pub fn is_empty(&self) -> bool {
558        self.len() == 0
559    }
560
561    /// Iterate over column sort specs.
562    pub fn iter(&self) -> TableSortSpecsIter<'_> {
563        TableSortSpecsIter {
564            specs: self,
565            index: 0,
566        }
567    }
568}
569
570/// Iterator over [`TableColumnSortSpec`].
571pub struct TableSortSpecsIter<'a> {
572    specs: &'a TableSortSpecs<'a>,
573    index: usize,
574}
575
576impl<'a> Iterator for TableSortSpecsIter<'a> {
577    type Item = TableColumnSortSpec;
578    fn next(&mut self) -> Option<Self::Item> {
579        if self.index >= self.specs.len() {
580            return None;
581        }
582        unsafe {
583            let ptr = (*self.specs.raw).Specs;
584            if ptr.is_null() {
585                return None;
586            }
587            let spec = &*ptr.add(self.index);
588            self.index += 1;
589            let d = spec.SortDirection as u8;
590            let dir = if d == sys::ImGuiSortDirection_None as u8 {
591                SortDirection::None
592            } else if d == sys::ImGuiSortDirection_Ascending as u8 {
593                SortDirection::Ascending
594            } else if d == sys::ImGuiSortDirection_Descending as u8 {
595                SortDirection::Descending
596            } else {
597                SortDirection::None
598            };
599            Some(TableColumnSortSpec {
600                column_user_id: spec.ColumnUserID,
601                column_index: spec.ColumnIndex,
602                sort_order: spec.SortOrder,
603                sort_direction: dir,
604            })
605        }
606    }
607}
608
609/// Tracks a table that can be ended by calling `.end()` or by dropping
610#[must_use]
611pub struct TableToken<'ui> {
612    _ui: &'ui Ui,
613}
614
615impl<'ui> TableToken<'ui> {
616    /// Creates a new table token
617    fn new(ui: &'ui Ui) -> Self {
618        TableToken { _ui: ui }
619    }
620
621    /// Ends the table
622    pub fn end(self) {
623        // The drop implementation will handle the actual ending
624    }
625}
626
627impl<'ui> Drop for TableToken<'ui> {
628    fn drop(&mut self) {
629        unsafe {
630            sys::igEndTable();
631        }
632    }
633}
634
635// ============================================================================
636// Additional table convenience APIs
637// ============================================================================
638
639/// Safe description of a single angled header cell.
640#[derive(Copy, Clone, Debug, PartialEq)]
641pub struct TableHeaderData {
642    pub index: i16,
643    pub text_color: ImColor32,
644    pub bg_color0: ImColor32,
645    pub bg_color1: ImColor32,
646}
647
648impl TableHeaderData {
649    pub fn new(
650        index: i16,
651        text_color: ImColor32,
652        bg_color0: ImColor32,
653        bg_color1: ImColor32,
654    ) -> Self {
655        Self {
656            index,
657            text_color,
658            bg_color0,
659            bg_color1,
660        }
661    }
662}
663impl Ui {
664    /// Maximum label width used for angled headers (when enabled in style/options).
665    #[doc(alias = "TableGetHeaderAngledMaxLabelWidth")]
666    pub fn table_get_header_angled_max_label_width(&self) -> f32 {
667        unsafe { sys::igTableGetHeaderAngledMaxLabelWidth() }
668    }
669
670    /// Submit angled headers row (requires style/flags enabling angled headers).
671    #[doc(alias = "TableAngledHeadersRow")]
672    pub fn table_angled_headers_row(&self) {
673        unsafe { sys::igTableAngledHeadersRow() }
674    }
675
676    // Removed legacy TableAngledHeadersRowEx(flags) wrapper; use `table_angled_headers_row_ex_with_data`.
677
678    /// Submit angled headers row with explicit data (Ex variant).
679    ///
680    /// - `row_id`: ImGuiID for the row. Use 0 for automatic if not needed.
681    /// - `angle`: Angle in radians for headers.
682    /// - `max_label_width`: Maximum label width for angled headers.
683    /// - `headers`: Per-column header data.
684    pub fn table_angled_headers_row_ex_with_data(
685        &self,
686        row_id: u32,
687        angle: f32,
688        max_label_width: f32,
689        headers: &[TableHeaderData],
690    ) {
691        if headers.is_empty() {
692            unsafe { sys::igTableAngledHeadersRow() }
693            return;
694        }
695        let count = match i32::try_from(headers.len()) {
696            Ok(n) => n,
697            Err(_) => return,
698        };
699        let mut data: Vec<sys::ImGuiTableHeaderData> = Vec::with_capacity(headers.len());
700        for h in headers {
701            data.push(sys::ImGuiTableHeaderData {
702                Index: h.index as sys::ImGuiTableColumnIdx,
703                TextColor: u32::from(h.text_color),
704                BgColor0: u32::from(h.bg_color0),
705                BgColor1: u32::from(h.bg_color1),
706            });
707        }
708        unsafe {
709            sys::igTableAngledHeadersRowEx(row_id, angle, max_label_width, data.as_ptr(), count);
710        }
711    }
712
713    /// Push background draw channel for the current table and return a token to pop it.
714    #[doc(alias = "TablePushBackgroundChannel")]
715    pub fn table_push_background_channel(&self) {
716        unsafe { sys::igTablePushBackgroundChannel() }
717    }
718
719    /// Pop background draw channel for the current table.
720    #[doc(alias = "TablePopBackgroundChannel")]
721    pub fn table_pop_background_channel(&self) {
722        unsafe { sys::igTablePopBackgroundChannel() }
723    }
724
725    /// Push column draw channel for the given column index and return a token to pop it.
726    #[doc(alias = "TablePushColumnChannel")]
727    pub fn table_push_column_channel(&self, column_n: i32) {
728        unsafe { sys::igTablePushColumnChannel(column_n) }
729    }
730
731    /// Pop column draw channel.
732    #[doc(alias = "TablePopColumnChannel")]
733    pub fn table_pop_column_channel(&self) {
734        unsafe { sys::igTablePopColumnChannel() }
735    }
736
737    /// Run a closure after pushing table background channel (auto-pop on return).
738    pub fn with_table_background_channel<R>(&self, f: impl FnOnce() -> R) -> R {
739        self.table_push_background_channel();
740        let result = f();
741        self.table_pop_background_channel();
742        result
743    }
744
745    /// Run a closure after pushing a table column channel (auto-pop on return).
746    pub fn with_table_column_channel<R>(&self, column_n: i32, f: impl FnOnce() -> R) -> R {
747        self.table_push_column_channel(column_n);
748        let result = f();
749        self.table_pop_column_channel();
750        result
751    }
752
753    /// Open the table context menu for a given column (use -1 for current/default).
754    #[doc(alias = "TableOpenContextMenu")]
755    pub fn table_open_context_menu(&self, column_n: Option<i32>) {
756        unsafe { sys::igTableOpenContextMenu(column_n.unwrap_or(-1)) }
757    }
758}
759
760// (Optional) RAII versions could be added later if desirable
761
762// ============================================================================
763// TableBuilder: ergonomic table construction
764// ============================================================================
765
766/// Builder for ImGui tables with columns + headers + sizing/freeze options.
767#[derive(Debug)]
768pub struct TableBuilder<'ui> {
769    ui: &'ui Ui,
770    id: Cow<'ui, str>,
771    flags: TableFlags,
772    sizing_policy: Option<TableSizingPolicy>,
773    outer_size: [f32; 2],
774    inner_width: f32,
775    columns: Vec<TableColumnSetup<Cow<'ui, str>>>,
776    use_headers: bool,
777    freeze: Option<(i32, i32)>,
778}
779
780impl<'ui> TableBuilder<'ui> {
781    /// Create a new TableBuilder. Prefer using `Ui::table("id")`.
782    pub fn new(ui: &'ui Ui, str_id: impl Into<Cow<'ui, str>>) -> Self {
783        Self {
784            ui,
785            id: str_id.into(),
786            flags: TableFlags::NONE,
787            sizing_policy: None,
788            outer_size: [0.0, 0.0],
789            inner_width: 0.0,
790            columns: Vec::new(),
791            use_headers: false,
792            freeze: None,
793        }
794    }
795
796    /// Set table flags
797    pub fn flags(mut self, flags: TableFlags) -> Self {
798        self.flags = flags;
799        self
800    }
801
802    /// Set the table sizing policy.
803    pub fn sizing_policy(mut self, policy: TableSizingPolicy) -> Self {
804        self.sizing_policy = Some(policy);
805        self
806    }
807
808    /// Set outer size (width, height). Default [0,0]
809    pub fn outer_size(mut self, size: [f32; 2]) -> Self {
810        self.outer_size = size;
811        self
812    }
813
814    /// Set inner width. Default 0.0
815    pub fn inner_width(mut self, width: f32) -> Self {
816        self.inner_width = width;
817        self
818    }
819
820    /// Freeze columns/rows so they stay visible when scrolling
821    pub fn freeze(mut self, frozen_cols: i32, frozen_rows: i32) -> Self {
822        self.freeze = Some((frozen_cols, frozen_rows));
823        self
824    }
825
826    /// Begin defining a column using a chainable ColumnBuilder.
827    /// Call `.done()` to return to the TableBuilder.
828    pub fn column(self, name: impl Into<Cow<'ui, str>>) -> ColumnBuilder<'ui> {
829        ColumnBuilder::new(self, name)
830    }
831
832    /// Replace columns with provided list
833    pub fn columns<Name: Into<Cow<'ui, str>>>(
834        mut self,
835        cols: impl IntoIterator<Item = TableColumnSetup<Name>>,
836    ) -> Self {
837        self.columns.clear();
838        for c in cols {
839            self.columns.push(TableColumnSetup {
840                name: c.name.into(),
841                flags: c.flags,
842                width: c.width,
843                indent: c.indent,
844                user_id: c.user_id,
845            });
846        }
847        self
848    }
849
850    /// Add a single column setup
851    pub fn add_column<Name: Into<Cow<'ui, str>>>(mut self, col: TableColumnSetup<Name>) -> Self {
852        self.columns.push(TableColumnSetup {
853            name: col.name.into(),
854            flags: col.flags,
855            width: col.width,
856            indent: col.indent,
857            user_id: col.user_id,
858        });
859        self
860    }
861
862    /// Auto submit headers row from `TableSetupColumn()` entries
863    pub fn headers(mut self, enabled: bool) -> Self {
864        self.use_headers = enabled;
865        self
866    }
867
868    /// Build the table and run a closure to emit rows/cells
869    pub fn build(self, f: impl FnOnce(&Ui)) {
870        let mut options = TableOptions::from(self.flags);
871        if let Some(policy) = self.sizing_policy {
872            options = options.sizing_policy(policy);
873        }
874        let Some(token) = self.ui.begin_table_with_sizing(
875            self.id.as_ref(),
876            self.columns.len(),
877            options,
878            self.outer_size,
879            self.inner_width,
880        ) else {
881            return;
882        };
883
884        if let Some((fc, fr)) = self.freeze {
885            self.ui.table_setup_scroll_freeze(fc, fr);
886        }
887
888        if !self.columns.is_empty() {
889            for col in &self.columns {
890                self.ui.table_setup_column_with_indent(
891                    col.name.as_ref(),
892                    col.flags,
893                    col.width,
894                    col.indent,
895                    col.user_id,
896                );
897            }
898            if self.use_headers {
899                self.ui.table_headers_row();
900            }
901        }
902
903        f(self.ui);
904
905        // drop token to end table
906        token.end();
907    }
908}
909
910/// Chainable builder for a single column. Use `.done()` to return to the table builder.
911#[derive(Debug)]
912pub struct ColumnBuilder<'ui> {
913    parent: TableBuilder<'ui>,
914    name: Cow<'ui, str>,
915    flags: TableColumnFlags,
916    width: Option<TableColumnWidth>,
917    indent: Option<TableColumnIndent>,
918    user_id: u32,
919}
920
921impl<'ui> ColumnBuilder<'ui> {
922    fn new(parent: TableBuilder<'ui>, name: impl Into<Cow<'ui, str>>) -> Self {
923        Self {
924            parent,
925            name: name.into(),
926            flags: TableColumnFlags::NONE,
927            width: None,
928            indent: None,
929            user_id: 0,
930        }
931    }
932
933    /// Set column flags.
934    pub fn flags(mut self, flags: TableColumnFlags) -> Self {
935        self.flags = flags;
936        self
937    }
938
939    /// Set fixed width or stretch weight (ImGui uses same field for both).
940    pub fn width(mut self, width: f32) -> Self {
941        self.width = Some(TableColumnWidth::Fixed(width));
942        self
943    }
944
945    /// Alias of `width()` to express stretch weights.
946    pub fn weight(mut self, weight: f32) -> Self {
947        self.width = Some(TableColumnWidth::Stretch(weight));
948        self
949    }
950
951    /// Set this column's indentation policy.
952    pub fn indent(mut self, indent: TableColumnIndent) -> Self {
953        self.indent = Some(indent);
954        self
955    }
956
957    /// Enable or disable indentation for this column.
958    pub fn indent_enabled(mut self, enabled: bool) -> Self {
959        self.indent = Some(if enabled {
960            TableColumnIndent::Enable
961        } else {
962            TableColumnIndent::Disable
963        });
964        self
965    }
966
967    /// Toggle angled header flag.
968    pub fn angled_header(mut self, enabled: bool) -> Self {
969        if enabled {
970            self.flags.insert(TableColumnFlags::ANGLED_HEADER);
971        } else {
972            self.flags.remove(TableColumnFlags::ANGLED_HEADER);
973        }
974        self
975    }
976
977    /// Set user id for this column.
978    pub fn user_id(mut self, id: u32) -> Self {
979        self.user_id = id;
980        self
981    }
982
983    /// Finish this column and return to the table builder.
984    pub fn done(mut self) -> TableBuilder<'ui> {
985        self.parent.columns.push(TableColumnSetup {
986            name: self.name,
987            flags: self.flags,
988            width: self.width,
989            indent: self.indent,
990            user_id: self.user_id,
991        });
992        self.parent
993    }
994}