1use crate::ui::Ui;
2use crate::widget::table::{
3 SortDirection, TABLE_MAX_COLUMNS, TableBgTarget, TableBuilder, TableColumnFlags,
4 TableColumnIndent, TableColumnIndex, TableColumnRef, TableColumnSetup, TableColumnStateFlags,
5 TableColumnWidth, TableFlags, TableHoveredColumn, TableHoveredRow, TableOptions, TableRowFlags,
6 TableRowIndex, TableSortSpecs, TableToken, assert_current_table, assert_current_table_cell,
7 assert_current_table_has_flags, assert_current_table_row, assert_non_negative_finite_f32,
8 assert_table_column_width_phase, assert_table_setup_phase, assert_valid_table_column,
9 assert_valid_table_column_raw_in, current_table_if_any, optional_user_id_raw,
10 resolve_table_column, table_column_count_to_i32, table_freeze_count_to_i32,
11};
12use crate::{Id, sys};
13use std::borrow::Cow;
14use std::ffi::CStr;
15
16impl Ui {
18 pub fn table<'ui>(&'ui self, str_id: impl Into<Cow<'ui, str>>) -> TableBuilder<'ui> {
39 TableBuilder::new(self, str_id)
40 }
41 #[must_use = "if return is dropped immediately, table is ended immediately."]
47 pub fn begin_table(
48 &self,
49 str_id: impl AsRef<str>,
50 column_count: usize,
51 ) -> Option<TableToken<'_>> {
52 self.begin_table_with_flags(str_id, column_count, TableFlags::NONE)
53 }
54
55 #[must_use = "if return is dropped immediately, table is ended immediately."]
57 pub fn begin_table_with_flags(
58 &self,
59 str_id: impl AsRef<str>,
60 column_count: usize,
61 flags: impl Into<TableOptions>,
62 ) -> Option<TableToken<'_>> {
63 self.begin_table_with_sizing(str_id, column_count, flags, [0.0, 0.0], 0.0)
64 }
65
66 #[must_use = "if return is dropped immediately, table is ended immediately."]
69 pub fn begin_table_with_sizing(
70 &self,
71 str_id: impl AsRef<str>,
72 column_count: usize,
73 flags: impl Into<TableOptions>,
74 outer_size: impl Into<[f32; 2]>,
75 inner_width: f32,
76 ) -> Option<TableToken<'_>> {
77 let options = flags.into();
78 options.validate("Ui::begin_table_with_sizing()");
79 assert!(
80 inner_width.is_finite(),
81 "Ui::begin_table_with_sizing() inner_width must be finite"
82 );
83 assert!(
84 !options.flags.contains(TableFlags::SCROLL_X) || inner_width >= 0.0,
85 "Ui::begin_table_with_sizing() inner_width must be non-negative when SCROLL_X is enabled"
86 );
87 let outer_size = outer_size.into();
88 assert!(
89 outer_size[0].is_finite() && outer_size[1].is_finite(),
90 "Ui::begin_table_with_sizing() outer_size must contain finite values"
91 );
92 let str_id_ptr = self.scratch_txt(str_id);
93 let outer_size_vec: sys::ImVec2 = outer_size.into();
94 let column_count = table_column_count_to_i32(column_count);
95
96 let should_render = unsafe {
97 sys::igBeginTable(
98 str_id_ptr,
99 column_count,
100 options.raw(),
101 outer_size_vec,
102 inner_width,
103 )
104 };
105
106 if should_render {
107 Some(TableToken::new(self))
108 } else {
109 None
110 }
111 }
112
113 #[must_use = "if return is dropped immediately, table is ended immediately."]
118 pub fn begin_table_header<Name: AsRef<str>, const N: usize>(
119 &self,
120 str_id: impl AsRef<str>,
121 column_data: [TableColumnSetup<Name>; N],
122 ) -> Option<TableToken<'_>> {
123 self.begin_table_header_with_flags(str_id, column_data, TableFlags::NONE)
124 }
125
126 #[must_use = "if return is dropped immediately, table is ended immediately."]
131 pub fn begin_table_header_with_flags<Name: AsRef<str>, const N: usize>(
132 &self,
133 str_id: impl AsRef<str>,
134 column_data: [TableColumnSetup<Name>; N],
135 flags: impl Into<TableOptions>,
136 ) -> Option<TableToken<'_>> {
137 if let Some(token) = self.begin_table_with_flags(str_id, N, flags) {
138 for column in &column_data {
140 self.table_setup_column_with_indent(
141 &column.name,
142 column.flags,
143 column.width,
144 column.indent,
145 column.user_id,
146 );
147 }
148 self.table_headers_row();
149 Some(token)
150 } else {
151 None
152 }
153 }
154
155 pub fn table_setup_column(
157 &self,
158 label: impl AsRef<str>,
159 flags: TableColumnFlags,
160 width: Option<TableColumnWidth>,
161 user_id: Option<Id>,
162 ) {
163 self.table_setup_column_with_indent(label, flags, width, None, user_id);
164 }
165
166 pub fn table_setup_column_with_indent(
168 &self,
169 label: impl AsRef<str>,
170 flags: TableColumnFlags,
171 width: Option<TableColumnWidth>,
172 indent: Option<TableColumnIndent>,
173 user_id: Option<Id>,
174 ) {
175 let table = assert_current_table("Ui::table_setup_column_with_indent()");
176 assert!(
177 unsafe { i32::from((*table).DeclColumnsCount) < (*table).ColumnsCount },
178 "Ui::table_setup_column_with_indent() called more times than the table column count"
179 );
180 assert_table_setup_phase("Ui::table_setup_column_with_indent()");
181 flags.validate_for_setup("Ui::table_setup_column_with_indent()", width, indent);
182 let init_width_or_weight = width.map_or(0.0, TableColumnWidth::value);
183 assert!(
184 init_width_or_weight.is_finite(),
185 "Ui::table_setup_column_with_indent() width or weight must be finite"
186 );
187 let label_ptr = self.scratch_txt(label);
188 let raw_flags = flags.bits()
189 | width.map_or(0, TableColumnWidth::raw_flags)
190 | indent.map_or(0, TableColumnIndent::raw_flags);
191 let user_id = optional_user_id_raw(user_id, "Ui::table_setup_column_with_indent()");
192 unsafe {
193 sys::igTableSetupColumn(label_ptr, raw_flags, init_width_or_weight, user_id);
194 }
195 }
196
197 pub fn table_setup_column_fixed_width(
199 &self,
200 label: impl AsRef<str>,
201 flags: TableColumnFlags,
202 width: f32,
203 user_id: Option<Id>,
204 ) {
205 self.table_setup_column(label, flags, Some(TableColumnWidth::Fixed(width)), user_id);
206 }
207
208 pub fn table_setup_column_stretch_weight(
210 &self,
211 label: impl AsRef<str>,
212 flags: TableColumnFlags,
213 weight: f32,
214 user_id: Option<Id>,
215 ) {
216 self.table_setup_column(
217 label,
218 flags,
219 Some(TableColumnWidth::Stretch(weight)),
220 user_id,
221 );
222 }
223
224 pub fn table_headers_row(&self) {
226 assert_current_table("Ui::table_headers_row()");
227 unsafe {
228 sys::igTableHeadersRow();
229 }
230 }
231
232 pub fn table_next_column(&self) -> bool {
234 unsafe { sys::igTableNextColumn() }
235 }
236
237 pub fn table_set_column_index(&self, column: impl Into<TableColumnIndex>) -> bool {
239 let column = column.into();
240 let column_n = column.into_i32("Ui::table_set_column_index()");
241 if let Some(table) = current_table_if_any() {
242 assert_valid_table_column_raw_in(table, column_n, "Ui::table_set_column_index()");
243 }
244 unsafe { sys::igTableSetColumnIndex(column_n) }
245 }
246
247 pub fn table_next_row(&self) {
249 self.table_next_row_with_flags(TableRowFlags::NONE, 0.0);
250 }
251
252 pub fn table_next_row_with_flags(&self, flags: TableRowFlags, min_row_height: f32) {
254 unsafe {
255 sys::igTableNextRow(flags.bits(), min_row_height);
256 }
257 }
258
259 #[doc(alias = "TableSetupScrollFreeze")]
261 pub fn table_setup_scroll_freeze(&self, frozen_cols: usize, frozen_rows: usize) {
262 assert_table_setup_phase("Ui::table_setup_scroll_freeze()");
263 let frozen_cols = table_freeze_count_to_i32(
264 "Ui::table_setup_scroll_freeze()",
265 "frozen_cols",
266 frozen_cols,
267 TABLE_MAX_COLUMNS,
268 );
269 let frozen_rows = table_freeze_count_to_i32(
270 "Ui::table_setup_scroll_freeze()",
271 "frozen_rows",
272 frozen_rows,
273 128,
274 );
275 unsafe { sys::igTableSetupScrollFreeze(frozen_cols, frozen_rows) }
276 }
277
278 #[doc(alias = "TableHeader")]
280 pub fn table_header(&self, label: impl AsRef<str>) {
281 assert_current_table_cell("Ui::table_header()");
282 let label_ptr = self.scratch_txt(label);
283 unsafe { sys::igTableHeader(label_ptr) }
284 }
285
286 #[doc(alias = "TableGetColumnCount")]
288 pub fn table_get_column_count(&self) -> usize {
289 usize::try_from(unsafe { sys::igTableGetColumnCount() })
290 .expect("Dear ImGui returned a negative table column count")
291 }
292
293 #[doc(alias = "TableGetColumnIndex")]
295 pub fn table_get_column_index(&self) -> Option<TableColumnIndex> {
296 current_table_if_any()?;
297 let raw = unsafe { sys::igTableGetColumnIndex() };
298 (raw >= 0).then(|| TableColumnIndex::from_i32(raw, "Ui::table_get_column_index()"))
299 }
300
301 #[doc(alias = "TableGetRowIndex")]
303 pub fn table_get_row_index(&self) -> Option<TableRowIndex> {
304 current_table_if_any()?;
305 let raw = unsafe { sys::igTableGetRowIndex() };
306 (raw >= 0).then(|| TableRowIndex::from_i32(raw, "Ui::table_get_row_index()"))
307 }
308
309 #[doc(alias = "TableGetColumnName")]
311 pub fn table_get_column_name(&self, column: impl Into<TableColumnRef>) -> &str {
312 let column = column.into();
313 let column_n = match column {
314 TableColumnRef::Current => -1,
315 TableColumnRef::Index(index) => index.into_i32("Ui::table_get_column_name()"),
316 };
317 if current_table_if_any().is_some() {
318 resolve_table_column(column, "Ui::table_get_column_name()");
319 }
320 unsafe {
321 let ptr = sys::igTableGetColumnName_Int(column_n);
322 if ptr.is_null() {
323 ""
324 } else {
325 CStr::from_ptr(ptr).to_str().unwrap_or("")
326 }
327 }
328 }
329
330 #[doc(alias = "TableGetColumnFlags")]
332 pub fn table_get_column_flags(
333 &self,
334 column: impl Into<TableColumnRef>,
335 ) -> TableColumnStateFlags {
336 let column = column.into();
337 let column_n = match column {
338 TableColumnRef::Current => -1,
339 TableColumnRef::Index(index) => index.into_i32("Ui::table_get_column_flags()"),
340 };
341 if let Some(table) = current_table_if_any() {
342 let column_count = unsafe { (*table).ColumnsCount };
343 let resolved_column = match column {
344 TableColumnRef::Current => unsafe { (*table).CurrentColumn },
345 TableColumnRef::Index(_) => column_n,
346 };
347 assert!(
348 (0..column_count).contains(&resolved_column),
349 "Ui::table_get_column_flags() column index {resolved_column} is outside the current table column range 0..{column_count}"
350 );
351 }
352 unsafe { TableColumnStateFlags::from_bits_retain(sys::igTableGetColumnFlags(column_n)) }
353 }
354
355 #[doc(alias = "TableSetColumnEnabled")]
357 pub fn table_set_column_enabled(&self, column: impl Into<TableColumnRef>, enabled: bool) {
358 assert_current_table_has_flags(TableFlags::HIDEABLE, "Ui::table_set_column_enabled()");
359 let column = column.into();
360 let column_n = match column {
361 TableColumnRef::Current => -1,
362 TableColumnRef::Index(index) => index.into_i32("Ui::table_set_column_enabled()"),
363 };
364 resolve_table_column(column, "Ui::table_set_column_enabled()");
365 unsafe { sys::igTableSetColumnEnabled(column_n, enabled) }
366 }
367
368 #[doc(alias = "TableGetHoveredColumn")]
370 pub fn table_get_hovered_column(&self) -> TableHoveredColumn {
371 let raw = unsafe { sys::igTableGetHoveredColumn() };
372 if raw < 0 {
373 return TableHoveredColumn::None;
374 }
375 if let Some(table) = current_table_if_any() {
376 let column_count = unsafe { (*table).ColumnsCount };
377 if raw == column_count {
378 return TableHoveredColumn::UnusedSpace;
379 }
380 }
381 TableHoveredColumn::Column(TableColumnIndex::from_i32(
382 raw,
383 "Ui::table_get_hovered_column()",
384 ))
385 }
386
387 #[doc(alias = "TableSetColumnWidth")]
389 pub fn table_set_column_width(&self, column: impl Into<TableColumnIndex>, width: f32) {
390 assert_table_column_width_phase("Ui::table_set_column_width()");
391 let column_n = assert_valid_table_column(column.into(), "Ui::table_set_column_width()");
392 assert_non_negative_finite_f32("Ui::table_set_column_width()", "width", width);
393 unsafe { sys::igTableSetColumnWidth(column_n, width) }
394 }
395
396 #[doc(alias = "TableSetBgColor")]
401 pub fn table_set_cell_bg_color_u32(&self, color: u32, column: impl Into<TableColumnRef>) {
402 let column = column.into();
403 assert_current_table_row("Ui::table_set_cell_bg_color_u32()");
404 let column_n = match column {
405 TableColumnRef::Current => -1,
406 TableColumnRef::Index(index) => index.into_i32("Ui::table_set_cell_bg_color_u32()"),
407 };
408 resolve_table_column(column, "Ui::table_set_cell_bg_color_u32()");
409 unsafe { sys::igTableSetBgColor(TableBgTarget::CellBg as i32, color, column_n) }
410 }
411
412 pub fn table_set_cell_bg_color(&self, rgba: [f32; 4], column: impl Into<TableColumnRef>) {
414 let col = crate::colors::Color::from_array(rgba).to_imgui_u32();
415 self.table_set_cell_bg_color_u32(col, column);
416 }
417
418 #[doc(alias = "TableSetBgColor")]
420 pub fn table_set_row_bg0_color_u32(&self, color: u32) {
421 assert_current_table_row("Ui::table_set_row_bg0_color_u32()");
422 unsafe { sys::igTableSetBgColor(TableBgTarget::RowBg0 as i32, color, -1) }
423 }
424
425 pub fn table_set_row_bg0_color(&self, rgba: [f32; 4]) {
427 let col = crate::colors::Color::from_array(rgba).to_imgui_u32();
428 self.table_set_row_bg0_color_u32(col);
429 }
430
431 #[doc(alias = "TableSetBgColor")]
433 pub fn table_set_row_bg1_color_u32(&self, color: u32) {
434 assert_current_table_row("Ui::table_set_row_bg1_color_u32()");
435 unsafe { sys::igTableSetBgColor(TableBgTarget::RowBg1 as i32, color, -1) }
436 }
437
438 pub fn table_set_row_bg1_color(&self, rgba: [f32; 4]) {
440 let col = crate::colors::Color::from_array(rgba).to_imgui_u32();
441 self.table_set_row_bg1_color_u32(col);
442 }
443
444 #[doc(alias = "TableGetHoveredRow")]
446 pub fn table_get_hovered_row(&self) -> TableHoveredRow {
447 if current_table_if_any().is_none() {
448 return TableHoveredRow::None;
449 }
450 let raw = unsafe { sys::igTableGetHoveredRow() };
451 if raw < 0 {
452 return TableHoveredRow::None;
453 }
454 TableHoveredRow::Row(TableRowIndex::from_i32(raw, "Ui::table_get_hovered_row()"))
455 }
456
457 #[doc(alias = "TableGetHeaderRowHeight")]
459 pub fn table_get_header_row_height(&self) -> f32 {
460 unsafe { sys::igTableGetHeaderRowHeight() }
461 }
462
463 #[doc(alias = "TableSetColumnSortDirection")]
465 pub fn table_set_column_sort_direction(
466 &self,
467 column: impl Into<TableColumnIndex>,
468 dir: SortDirection,
469 append_to_sort_specs: bool,
470 ) {
471 let column_n =
472 assert_valid_table_column(column.into(), "Ui::table_set_column_sort_direction()");
473 unsafe { sys::igTableSetColumnSortDirection(column_n, dir.into(), append_to_sort_specs) }
474 }
475
476 #[doc(alias = "TableGetSortSpecs")]
480 pub fn table_get_sort_specs(&self) -> Option<TableSortSpecs<'_>> {
481 unsafe {
482 let ptr = sys::igTableGetSortSpecs();
483 if ptr.is_null() {
484 None
485 } else {
486 Some(TableSortSpecs::from_raw(ptr))
487 }
488 }
489 }
490}