Skip to main content

dear_imgui_rs/widget/table/
headers.rs

1use crate::draw::ImColor32;
2use crate::internal::len_i32;
3use crate::sys;
4use crate::ui::Ui;
5use crate::widget::table::{
6    TableColumnIndex, TableContextMenuTarget, assert_current_table, assert_current_table_cell,
7    assert_non_negative_finite_f32, assert_table_before_first_row, assert_valid_table_column,
8    assert_valid_table_column_in,
9};
10
11use super::tokens::TableChannelGuard;
12
13/// Safe description of a single angled header cell.
14#[derive(Copy, Clone, Debug, PartialEq)]
15pub struct TableHeaderData {
16    pub index: TableColumnIndex,
17    pub text_color: ImColor32,
18    pub bg_color0: ImColor32,
19    pub bg_color1: ImColor32,
20}
21
22impl TableHeaderData {
23    pub fn new(
24        index: impl Into<TableColumnIndex>,
25        text_color: ImColor32,
26        bg_color0: ImColor32,
27        bg_color1: ImColor32,
28    ) -> Self {
29        Self {
30            index: index.into(),
31            text_color,
32            bg_color0,
33            bg_color1,
34        }
35    }
36}
37impl Ui {
38    /// Maximum label width used for angled headers when enabled in style/options.
39    ///
40    /// # Panics
41    ///
42    /// Panics outside a table.
43    #[doc(alias = "TableGetHeaderAngledMaxLabelWidth")]
44    pub fn table_get_header_angled_max_label_width(&self) -> f32 {
45        self.run_with_bound_context(|| {
46            assert_current_table("Ui::table_get_header_angled_max_label_width()");
47            unsafe { sys::igTableGetHeaderAngledMaxLabelWidth() }
48        })
49    }
50
51    /// Submit an angled headers row (requires style/flags enabling angled headers).
52    ///
53    /// # Panics
54    ///
55    /// Panics outside a table, after the first row has started, or while a table draw-channel scope
56    /// is active.
57    #[doc(alias = "TableAngledHeadersRow")]
58    pub fn table_angled_headers_row(&self) {
59        self.run_with_bound_context(|| {
60            assert_table_before_first_row("Ui::table_angled_headers_row()");
61            self.assert_no_active_table_channel("Ui::table_angled_headers_row()");
62            unsafe { sys::igTableAngledHeadersRow() };
63        });
64    }
65
66    // Removed legacy TableAngledHeadersRowEx(flags) wrapper; use `table_angled_headers_row_ex_with_data`.
67
68    /// Submit angled headers row with explicit data (Ex variant).
69    ///
70    /// - `row_id`: ImGuiID for the row. Use 0 for automatic if not needed.
71    /// - `angle`: Angle in radians for headers.
72    /// - `max_label_width`: Maximum label width for angled headers.
73    /// - `headers`: Per-column header data.
74    ///
75    /// # Panics
76    ///
77    /// Panics outside a table, after the first row has started, while a table draw-channel scope is
78    /// active, for a non-finite/out-of-range angle, for a negative/non-finite maximum width, for an
79    /// invalid column, or when `headers` are not ordered left-to-right without duplicates.
80    pub fn table_angled_headers_row_ex_with_data(
81        &self,
82        row_id: u32,
83        angle: f32,
84        max_label_width: f32,
85        headers: &[TableHeaderData],
86    ) {
87        assert!(
88            angle.is_finite(),
89            "Ui::table_angled_headers_row_ex_with_data() angle must be finite"
90        );
91        assert!(
92            (-std::f32::consts::FRAC_PI_2..std::f32::consts::FRAC_PI_2).contains(&angle),
93            "Ui::table_angled_headers_row_ex_with_data() angle must be between -PI/2 and PI/2"
94        );
95        assert_non_negative_finite_f32(
96            "Ui::table_angled_headers_row_ex_with_data()",
97            "max_label_width",
98            max_label_width,
99        );
100        if headers.is_empty() {
101            self.table_angled_headers_row();
102            return;
103        }
104        let count = len_i32(
105            "Ui::table_angled_headers_row_ex_with_data()",
106            "headers",
107            headers.len(),
108        );
109        let mut data: Vec<sys::ImGuiTableHeaderData> = Vec::with_capacity(headers.len());
110        self.run_with_bound_context(|| {
111            let table =
112                assert_table_before_first_row("Ui::table_angled_headers_row_ex_with_data()");
113            self.assert_no_active_table_channel(
114                "Ui::table_angled_headers_row_ex_with_data()",
115            );
116            let columns = unsafe { (*table).Columns.Data };
117            assert!(
118                !columns.is_null(),
119                "Ui::table_angled_headers_row_ex_with_data() table columns are unavailable"
120            );
121            let mut previous_display_order = -1;
122            for h in headers {
123                let column_n = assert_valid_table_column_in(
124                    table,
125                    h.index,
126                    "Ui::table_angled_headers_row_ex_with_data()",
127                );
128                let display_order = i32::from(unsafe {
129                    (*columns.add(column_n as usize)).DisplayOrder
130                });
131                assert!(
132                    display_order > previous_display_order,
133                    "Ui::table_angled_headers_row_ex_with_data() headers must be ordered left to right without duplicates"
134                );
135                previous_display_order = display_order;
136                data.push(sys::ImGuiTableHeaderData {
137                    Index: h
138                        .index
139                        .into_imgui_column_idx("Ui::table_angled_headers_row_ex_with_data()"),
140                    TextColor: u32::from(h.text_color),
141                    BgColor0: u32::from(h.bg_color0),
142                    BgColor1: u32::from(h.bg_color1),
143                });
144            }
145            unsafe {
146                sys::igTableAngledHeadersRowEx(
147                    row_id,
148                    angle,
149                    max_label_width,
150                    data.as_ptr(),
151                    count,
152                );
153            }
154        });
155    }
156
157    /// Run a closure while drawing into the current table's background channel.
158    ///
159    /// The channel cannot escape this closure. Row, column, nested-channel, and table-end
160    /// transitions are rejected before FFI while it is active.
161    ///
162    /// # Panics
163    ///
164    /// Panics if there is no current table cell or another table channel is active.
165    #[doc(
166        alias = "TablePushBackgroundChannel",
167        alias = "TablePopBackgroundChannel"
168    )]
169    pub fn with_table_background_channel<R>(&self, f: impl FnOnce() -> R) -> R {
170        self.run_with_bound_context(|| {
171            assert_current_table_cell("Ui::with_table_background_channel()");
172            self.assert_no_active_table_channel("Ui::with_table_background_channel()");
173            unsafe { sys::igTablePushBackgroundChannel() };
174        });
175        let guard = TableChannelGuard::background(self);
176        let result = f();
177        drop(guard);
178        result
179    }
180
181    /// Run a closure while drawing into a selected table column channel.
182    ///
183    /// The channel cannot escape this closure. Row, column, nested-channel, and table-end
184    /// transitions are rejected before FFI while it is active.
185    ///
186    /// # Panics
187    ///
188    /// Panics if there is no current table cell, `column` is invalid, or another table channel is
189    /// active.
190    #[doc(alias = "TablePushColumnChannel", alias = "TablePopColumnChannel")]
191    pub fn with_table_column_channel<R>(
192        &self,
193        column: impl Into<TableColumnIndex>,
194        f: impl FnOnce() -> R,
195    ) -> R {
196        let column = column.into();
197        self.run_with_bound_context(|| {
198            assert_current_table_cell("Ui::with_table_column_channel()");
199            self.assert_no_active_table_channel("Ui::with_table_column_channel()");
200            let column_n = assert_valid_table_column(column, "Ui::with_table_column_channel()");
201            unsafe { sys::igTablePushColumnChannel(column_n) };
202        });
203        let guard = TableChannelGuard::column(self);
204        let result = f();
205        drop(guard);
206        result
207    }
208
209    /// Open the table context menu for the current/default column.
210    ///
211    /// # Panics
212    ///
213    /// Panics outside a table or when an explicit column is outside the current table.
214    #[doc(alias = "TableOpenContextMenu")]
215    pub fn table_open_context_menu(&self, target: impl Into<TableContextMenuTarget>) {
216        let target = target.into();
217        self.run_with_bound_context(|| {
218            let table = assert_current_table("Ui::table_open_context_menu()");
219            let column_n = match target {
220                TableContextMenuTarget::CurrentColumn => -1,
221                TableContextMenuTarget::Column(index) => {
222                    assert_valid_table_column_in(table, index, "Ui::table_open_context_menu()")
223                }
224                TableContextMenuTarget::Table => unsafe { (*table).ColumnsCount },
225            };
226            unsafe { sys::igTableOpenContextMenu(column_n) }
227        });
228    }
229}