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