1use crate::sys;
2use crate::ui::Ui;
3use crate::widget::table::{
4 SortDirection, TABLE_MAX_COLUMNS, TableBgTarget, TableBuilder, TableColumnFlags,
5 TableColumnIndent, TableColumnIndex, TableColumnRef, TableColumnSetup, TableColumnStateFlags,
6 TableColumnUserData, TableColumnWidth, TableFlags, TableHoveredColumn, TableHoveredRow,
7 TableOptions, TableRowFlags, TableRowIndex, TableSortSpecs, TableToken, assert_current_table,
8 assert_current_table_cell, assert_current_table_has_flags, assert_current_table_row,
9 assert_non_negative_finite_f32, assert_table_column_width_phase, assert_table_setup_phase,
10 assert_valid_table_column, assert_valid_table_column_raw_in, current_table_if_any,
11 resolve_table_column, table_column_count_to_i32, table_freeze_count_to_i32,
12};
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."]
51 #[doc(alias = "BeginTable")]
52 pub fn begin_table(
53 &self,
54 str_id: impl AsRef<str>,
55 column_count: usize,
56 ) -> Option<TableToken<'_>> {
57 self.begin_table_with_flags(str_id, column_count, TableFlags::NONE)
58 }
59
60 #[must_use = "if return is dropped immediately, table is ended immediately."]
66 pub fn begin_table_with_flags(
67 &self,
68 str_id: impl AsRef<str>,
69 column_count: usize,
70 flags: impl Into<TableOptions>,
71 ) -> Option<TableToken<'_>> {
72 self.begin_table_with_sizing(str_id, column_count, flags, [0.0, 0.0], 0.0)
73 }
74
75 #[must_use = "if return is dropped immediately, table is ended immediately."]
84 pub fn begin_table_with_sizing(
85 &self,
86 str_id: impl AsRef<str>,
87 column_count: usize,
88 flags: impl Into<TableOptions>,
89 outer_size: impl Into<[f32; 2]>,
90 inner_width: f32,
91 ) -> Option<TableToken<'_>> {
92 let options = flags.into();
93 options.validate("Ui::begin_table_with_sizing()");
94 assert!(
95 inner_width.is_finite(),
96 "Ui::begin_table_with_sizing() inner_width must be finite"
97 );
98 assert!(
99 !options.flags.contains(TableFlags::SCROLL_X) || inner_width >= 0.0,
100 "Ui::begin_table_with_sizing() inner_width must be non-negative when SCROLL_X is enabled"
101 );
102 let outer_size = outer_size.into();
103 assert!(
104 outer_size[0].is_finite() && outer_size[1].is_finite(),
105 "Ui::begin_table_with_sizing() outer_size must contain finite values"
106 );
107 let str_id_ptr = self.scratch_txt(str_id);
108 let outer_size_vec: sys::ImVec2 = outer_size.into();
109 let column_count = table_column_count_to_i32(column_count);
110
111 let should_render = self.run_with_bound_context(|| {
112 self.assert_no_active_table_channel("Ui::begin_table_with_sizing()");
113 unsafe {
114 sys::igBeginTable(
115 str_id_ptr,
116 column_count,
117 options.raw(),
118 outer_size_vec,
119 inner_width,
120 )
121 }
122 });
123
124 if should_render {
125 Some(TableToken::new(self))
126 } else {
127 None
128 }
129 }
130
131 #[must_use = "if return is dropped immediately, table is ended immediately."]
141 pub fn begin_table_header<Name: AsRef<str>, const N: usize>(
142 &self,
143 str_id: impl AsRef<str>,
144 column_data: [TableColumnSetup<Name>; N],
145 ) -> Option<TableToken<'_>> {
146 self.begin_table_header_with_flags(str_id, column_data, TableFlags::NONE)
147 }
148
149 #[must_use = "if return is dropped immediately, table is ended immediately."]
158 pub fn begin_table_header_with_flags<Name: AsRef<str>, const N: usize>(
159 &self,
160 str_id: impl AsRef<str>,
161 column_data: [TableColumnSetup<Name>; N],
162 flags: impl Into<TableOptions>,
163 ) -> Option<TableToken<'_>> {
164 if let Some(token) = self.begin_table_with_flags(str_id, N, flags) {
165 for column in &column_data {
167 self.table_setup_column_with_indent_and_user_data(
168 &column.name,
169 column.flags,
170 column.width,
171 column.indent,
172 column.user_data,
173 );
174 }
175 self.table_headers_row();
176 Some(token)
177 } else {
178 None
179 }
180 }
181
182 #[doc(alias = "TableSetupColumn")]
189 pub fn table_setup_column(
190 &self,
191 label: impl AsRef<str>,
192 flags: TableColumnFlags,
193 width: Option<TableColumnWidth>,
194 ) {
195 self.table_setup_column_with_indent(label, flags, width, None);
196 }
197
198 pub fn table_setup_column_with_user_data(
204 &self,
205 label: impl AsRef<str>,
206 flags: TableColumnFlags,
207 width: Option<TableColumnWidth>,
208 user_data: impl Into<TableColumnUserData>,
209 ) {
210 self.table_setup_column_with_indent_and_user_data(label, flags, width, None, user_data);
211 }
212
213 pub fn table_setup_column_with_indent(
219 &self,
220 label: impl AsRef<str>,
221 flags: TableColumnFlags,
222 width: Option<TableColumnWidth>,
223 indent: Option<TableColumnIndent>,
224 ) {
225 self.table_setup_column_with_indent_and_user_data(label, flags, width, indent, 0);
226 }
227
228 pub fn table_setup_column_with_indent_and_user_data(
236 &self,
237 label: impl AsRef<str>,
238 flags: TableColumnFlags,
239 width: Option<TableColumnWidth>,
240 indent: Option<TableColumnIndent>,
241 user_data: impl Into<TableColumnUserData>,
242 ) {
243 flags.validate_for_setup(
244 "Ui::table_setup_column_with_indent_and_user_data()",
245 width,
246 indent,
247 );
248 let init_width_or_weight = width.map_or(0.0, TableColumnWidth::value);
249 assert!(
250 init_width_or_weight.is_finite(),
251 "Ui::table_setup_column_with_indent_and_user_data() width or weight must be finite"
252 );
253 let label_ptr = self.scratch_txt(label);
254 let raw_flags = flags.bits()
255 | width.map_or(0, TableColumnWidth::raw_flags)
256 | indent.map_or(0, TableColumnIndent::raw_flags);
257 let user_data = user_data.into().get();
258 self.run_with_bound_context(|| {
259 let table = assert_current_table("Ui::table_setup_column_with_indent_and_user_data()");
260 assert!(
261 unsafe { i32::from((*table).DeclColumnsCount) < (*table).ColumnsCount },
262 "Ui::table_setup_column_with_indent_and_user_data() called more times than the table column count"
263 );
264 assert_table_setup_phase("Ui::table_setup_column_with_indent_and_user_data()");
265 unsafe {
266 sys::igTableSetupColumn(label_ptr, raw_flags, init_width_or_weight, user_data);
267 }
268 });
269 }
270
271 pub fn table_setup_column_fixed_width(
277 &self,
278 label: impl AsRef<str>,
279 flags: TableColumnFlags,
280 width: f32,
281 ) {
282 self.table_setup_column(label, flags, Some(TableColumnWidth::Fixed(width)));
283 }
284
285 pub fn table_setup_column_stretch_weight(
291 &self,
292 label: impl AsRef<str>,
293 flags: TableColumnFlags,
294 weight: f32,
295 ) {
296 self.table_setup_column(label, flags, Some(TableColumnWidth::Stretch(weight)));
297 }
298
299 #[doc(alias = "TableHeadersRow")]
306 pub fn table_headers_row(&self) {
307 self.run_with_bound_context(|| {
308 assert_current_table("Ui::table_headers_row()");
309 self.assert_no_active_table_channel("Ui::table_headers_row()");
310 unsafe {
311 sys::igTableHeadersRow();
312 }
313 });
314 }
315
316 #[doc(alias = "TableNextColumn")]
325 pub fn table_next_column(&self) -> bool {
326 self.run_with_bound_context(|| {
327 self.assert_no_active_table_channel("Ui::table_next_column()");
328 unsafe { sys::igTableNextColumn() }
329 })
330 }
331
332 #[doc(alias = "TableSetColumnIndex")]
341 pub fn table_set_column_index(&self, column: impl Into<TableColumnIndex>) -> bool {
342 let column = column.into();
343 let column_n = column.into_i32("Ui::table_set_column_index()");
344 self.run_with_bound_context(|| {
345 self.assert_no_active_table_channel("Ui::table_set_column_index()");
346 if let Some(table) = current_table_if_any() {
347 assert_valid_table_column_raw_in(table, column_n, "Ui::table_set_column_index()");
348 }
349 unsafe { sys::igTableSetColumnIndex(column_n) }
350 })
351 }
352
353 #[doc(alias = "TableNextRow")]
359 pub fn table_next_row(&self) {
360 self.table_next_row_with_flags(TableRowFlags::NONE, 0.0);
361 }
362
363 pub fn table_next_row_with_flags(&self, flags: TableRowFlags, min_row_height: f32) {
370 assert_non_negative_finite_f32(
371 "Ui::table_next_row_with_flags()",
372 "min_row_height",
373 min_row_height,
374 );
375 self.run_with_bound_context(|| {
376 assert_current_table("Ui::table_next_row_with_flags()");
377 self.assert_no_active_table_channel("Ui::table_next_row_with_flags()");
378 unsafe { sys::igTableNextRow(flags.bits(), min_row_height) };
379 });
380 }
381
382 #[doc(alias = "TableSetupScrollFreeze")]
389 pub fn table_setup_scroll_freeze(&self, frozen_cols: usize, frozen_rows: usize) {
390 let frozen_cols = table_freeze_count_to_i32(
391 "Ui::table_setup_scroll_freeze()",
392 "frozen_cols",
393 frozen_cols,
394 TABLE_MAX_COLUMNS,
395 );
396 let frozen_rows = table_freeze_count_to_i32(
397 "Ui::table_setup_scroll_freeze()",
398 "frozen_rows",
399 frozen_rows,
400 128,
401 );
402 self.run_with_bound_context(|| {
403 assert_table_setup_phase("Ui::table_setup_scroll_freeze()");
404 unsafe { sys::igTableSetupScrollFreeze(frozen_cols, frozen_rows) }
405 });
406 }
407
408 #[doc(alias = "TableHeader")]
414 pub fn table_header(&self, label: impl AsRef<str>) {
415 let label_ptr = self.scratch_txt(label);
416 self.run_with_bound_context(|| {
417 assert_current_table_cell("Ui::table_header()");
418 unsafe { sys::igTableHeader(label_ptr) }
419 });
420 }
421
422 #[doc(alias = "TableGetColumnCount")]
424 pub fn table_get_column_count(&self) -> usize {
425 usize::try_from(self.run_with_bound_context(|| unsafe { sys::igTableGetColumnCount() }))
426 .expect("Dear ImGui returned a negative table column count")
427 }
428
429 #[doc(alias = "TableGetColumnIndex")]
431 pub fn table_get_column_index(&self) -> Option<TableColumnIndex> {
432 self.run_with_bound_context(|| {
433 current_table_if_any()?;
434 let raw = unsafe { sys::igTableGetColumnIndex() };
435 (raw >= 0).then(|| TableColumnIndex::from_i32(raw, "Ui::table_get_column_index()"))
436 })
437 }
438
439 #[doc(alias = "TableGetRowIndex")]
441 pub fn table_get_row_index(&self) -> Option<TableRowIndex> {
442 self.run_with_bound_context(|| {
443 current_table_if_any()?;
444 let raw = unsafe { sys::igTableGetRowIndex() };
445 (raw >= 0).then(|| TableRowIndex::from_i32(raw, "Ui::table_get_row_index()"))
446 })
447 }
448
449 #[doc(alias = "TableGetColumnName")]
457 pub fn table_get_column_name(&self, column: impl Into<TableColumnRef>) -> &str {
458 let column = column.into();
459 let column_n = match column {
460 TableColumnRef::Current => -1,
461 TableColumnRef::Index(index) => index.into_i32("Ui::table_get_column_name()"),
462 };
463 self.run_with_bound_context(|| {
464 if current_table_if_any().is_some() {
465 resolve_table_column(column, "Ui::table_get_column_name()");
466 }
467 unsafe {
468 let ptr = sys::igTableGetColumnName_Int(column_n);
469 if ptr.is_null() {
470 ""
471 } else {
472 CStr::from_ptr(ptr).to_str().unwrap_or("")
473 }
474 }
475 })
476 }
477
478 #[doc(alias = "TableGetColumnFlags")]
486 pub fn table_get_column_flags(
487 &self,
488 column: impl Into<TableColumnRef>,
489 ) -> TableColumnStateFlags {
490 let column = column.into();
491 let column_n = match column {
492 TableColumnRef::Current => -1,
493 TableColumnRef::Index(index) => index.into_i32("Ui::table_get_column_flags()"),
494 };
495 self.run_with_bound_context(|| {
496 if let Some(table) = current_table_if_any() {
497 let column_count = unsafe { (*table).ColumnsCount };
498 let resolved_column = match column {
499 TableColumnRef::Current => unsafe { (*table).CurrentColumn },
500 TableColumnRef::Index(_) => column_n,
501 };
502 assert!(
503 (0..column_count).contains(&resolved_column),
504 "Ui::table_get_column_flags() column index {resolved_column} is outside the current table column range 0..{column_count}"
505 );
506 }
507 unsafe { TableColumnStateFlags::from_bits_retain(sys::igTableGetColumnFlags(column_n)) }
508 })
509 }
510
511 #[doc(alias = "TableSetColumnEnabled")]
518 pub fn table_set_column_enabled(&self, column: impl Into<TableColumnRef>, enabled: bool) {
519 let column = column.into();
520 let column_n = match column {
521 TableColumnRef::Current => -1,
522 TableColumnRef::Index(index) => index.into_i32("Ui::table_set_column_enabled()"),
523 };
524 self.run_with_bound_context(|| {
525 assert_current_table_has_flags(TableFlags::HIDEABLE, "Ui::table_set_column_enabled()");
526 resolve_table_column(column, "Ui::table_set_column_enabled()");
527 unsafe { sys::igTableSetColumnEnabled(column_n, enabled) }
528 });
529 }
530
531 #[doc(alias = "TableGetHoveredColumn")]
534 pub fn table_get_hovered_column(&self) -> TableHoveredColumn {
535 self.run_with_bound_context(|| {
536 let raw = unsafe { sys::igTableGetHoveredColumn() };
537 if raw < 0 {
538 return TableHoveredColumn::None;
539 }
540 if let Some(table) = current_table_if_any() {
541 let column_count = unsafe { (*table).ColumnsCount };
542 if raw == column_count {
543 return TableHoveredColumn::UnusedSpace;
544 }
545 }
546 TableHoveredColumn::Column(TableColumnIndex::from_i32(
547 raw,
548 "Ui::table_get_hovered_column()",
549 ))
550 })
551 }
552
553 #[doc(alias = "TableSetColumnWidth")]
560 pub fn table_set_column_width(&self, column: impl Into<TableColumnIndex>, width: f32) {
561 assert_non_negative_finite_f32("Ui::table_set_column_width()", "width", width);
562 let column = column.into();
563 self.run_with_bound_context(|| {
564 assert_table_column_width_phase("Ui::table_set_column_width()");
565 let column_n = assert_valid_table_column(column, "Ui::table_set_column_width()");
566 unsafe { sys::igTableSetColumnWidth(column_n, width) }
567 });
568 }
569
570 #[doc(alias = "TableSetBgColor")]
579 pub fn table_set_cell_bg_color_u32(&self, color: u32, column: impl Into<TableColumnRef>) {
580 let column = column.into();
581 let column_n = match column {
582 TableColumnRef::Current => -1,
583 TableColumnRef::Index(index) => index.into_i32("Ui::table_set_cell_bg_color_u32()"),
584 };
585 self.run_with_bound_context(|| {
586 assert_current_table_row("Ui::table_set_cell_bg_color_u32()");
587 resolve_table_column(column, "Ui::table_set_cell_bg_color_u32()");
588 unsafe { sys::igTableSetBgColor(TableBgTarget::CellBg as i32, color, column_n) }
589 });
590 }
591
592 pub fn table_set_cell_bg_color(&self, rgba: [f32; 4], column: impl Into<TableColumnRef>) {
598 let col = crate::colors::Color::from_array(rgba).to_imgui_u32();
599 self.table_set_cell_bg_color_u32(col, column);
600 }
601
602 #[doc(alias = "TableSetBgColor")]
608 pub fn table_set_row_bg0_color_u32(&self, color: u32) {
609 self.run_with_bound_context(|| {
610 assert_current_table_row("Ui::table_set_row_bg0_color_u32()");
611 unsafe { sys::igTableSetBgColor(TableBgTarget::RowBg0 as i32, color, -1) }
612 });
613 }
614
615 pub fn table_set_row_bg0_color(&self, rgba: [f32; 4]) {
621 let col = crate::colors::Color::from_array(rgba).to_imgui_u32();
622 self.table_set_row_bg0_color_u32(col);
623 }
624
625 #[doc(alias = "TableSetBgColor")]
631 pub fn table_set_row_bg1_color_u32(&self, color: u32) {
632 self.run_with_bound_context(|| {
633 assert_current_table_row("Ui::table_set_row_bg1_color_u32()");
634 unsafe { sys::igTableSetBgColor(TableBgTarget::RowBg1 as i32, color, -1) }
635 });
636 }
637
638 pub fn table_set_row_bg1_color(&self, rgba: [f32; 4]) {
644 let col = crate::colors::Color::from_array(rgba).to_imgui_u32();
645 self.table_set_row_bg1_color_u32(col);
646 }
647
648 #[doc(alias = "TableGetHoveredRow")]
650 pub fn table_get_hovered_row(&self) -> TableHoveredRow {
651 self.run_with_bound_context(|| {
652 if current_table_if_any().is_none() {
653 return TableHoveredRow::None;
654 }
655 let raw = unsafe { sys::igTableGetHoveredRow() };
656 if raw < 0 {
657 return TableHoveredRow::None;
658 }
659 TableHoveredRow::Row(TableRowIndex::from_i32(raw, "Ui::table_get_hovered_row()"))
660 })
661 }
662
663 #[doc(alias = "TableGetHeaderRowHeight")]
669 pub fn table_get_header_row_height(&self) -> f32 {
670 self.run_with_bound_context(|| {
671 assert_current_table("Ui::table_get_header_row_height()");
672 unsafe { sys::igTableGetHeaderRowHeight() }
673 })
674 }
675
676 #[doc(alias = "TableSetColumnSortDirection")]
683 pub fn table_set_column_sort_direction(
684 &self,
685 column: impl Into<TableColumnIndex>,
686 dir: SortDirection,
687 append_to_sort_specs: bool,
688 ) {
689 let column = column.into();
690 self.run_with_bound_context(|| {
691 let table = assert_current_table("Ui::table_set_column_sort_direction()");
692 let table_flags = TableFlags::from_bits_retain(unsafe { (*table).Flags });
693 assert!(
694 table_flags.contains(TableFlags::SORTABLE),
695 "Ui::table_set_column_sort_direction() requires the current table to have SORTABLE"
696 );
697 if dir == SortDirection::None {
698 assert!(
699 table_flags.contains(TableFlags::SORT_TRISTATE),
700 "Ui::table_set_column_sort_direction() requires SORT_TRISTATE for SortDirection::None"
701 );
702 }
703 let column_n =
704 assert_valid_table_column(column, "Ui::table_set_column_sort_direction()");
705 unsafe {
706 sys::igTableSetColumnSortDirection(column_n, dir.into(), append_to_sort_specs)
707 }
708 });
709 }
710
711 #[doc(alias = "TableGetSortSpecs")]
717 pub fn table_get_sort_specs(&self) -> Option<TableSortSpecs> {
718 self.run_with_bound_context(|| unsafe {
719 let table = current_table_if_any()?;
720 let ptr = sys::igTableGetSortSpecs();
721 if ptr.is_null() {
722 None
723 } else {
724 Some(TableSortSpecs::from_raw(self, table, ptr))
725 }
726 })
727 }
728}