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