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