dear_imgui/widget/
table.rs

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