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 = self.run_with_bound_context(|| 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 flags.validate_for_setup("Ui::table_setup_column_with_indent()", width, indent);
176 let init_width_or_weight = width.map_or(0.0, TableColumnWidth::value);
177 assert!(
178 init_width_or_weight.is_finite(),
179 "Ui::table_setup_column_with_indent() width or weight must be finite"
180 );
181 let label_ptr = self.scratch_txt(label);
182 let raw_flags = flags.bits()
183 | width.map_or(0, TableColumnWidth::raw_flags)
184 | indent.map_or(0, TableColumnIndent::raw_flags);
185 let user_id = optional_user_id_raw(user_id, "Ui::table_setup_column_with_indent()");
186 self.run_with_bound_context(|| {
187 let table = assert_current_table("Ui::table_setup_column_with_indent()");
188 assert!(
189 unsafe { i32::from((*table).DeclColumnsCount) < (*table).ColumnsCount },
190 "Ui::table_setup_column_with_indent() called more times than the table column count"
191 );
192 assert_table_setup_phase("Ui::table_setup_column_with_indent()");
193 unsafe {
194 sys::igTableSetupColumn(label_ptr, raw_flags, init_width_or_weight, user_id);
195 }
196 });
197 }
198
199 pub fn table_setup_column_fixed_width(
201 &self,
202 label: impl AsRef<str>,
203 flags: TableColumnFlags,
204 width: f32,
205 user_id: Option<Id>,
206 ) {
207 self.table_setup_column(label, flags, Some(TableColumnWidth::Fixed(width)), user_id);
208 }
209
210 pub fn table_setup_column_stretch_weight(
212 &self,
213 label: impl AsRef<str>,
214 flags: TableColumnFlags,
215 weight: f32,
216 user_id: Option<Id>,
217 ) {
218 self.table_setup_column(
219 label,
220 flags,
221 Some(TableColumnWidth::Stretch(weight)),
222 user_id,
223 );
224 }
225
226 pub fn table_headers_row(&self) {
228 self.run_with_bound_context(|| {
229 assert_current_table("Ui::table_headers_row()");
230 unsafe {
231 sys::igTableHeadersRow();
232 }
233 });
234 }
235
236 pub fn table_next_column(&self) -> bool {
238 self.run_with_bound_context(|| unsafe { sys::igTableNextColumn() })
239 }
240
241 pub fn table_set_column_index(&self, column: impl Into<TableColumnIndex>) -> bool {
243 let column = column.into();
244 let column_n = column.into_i32("Ui::table_set_column_index()");
245 self.run_with_bound_context(|| {
246 if let Some(table) = current_table_if_any() {
247 assert_valid_table_column_raw_in(table, column_n, "Ui::table_set_column_index()");
248 }
249 unsafe { sys::igTableSetColumnIndex(column_n) }
250 })
251 }
252
253 pub fn table_next_row(&self) {
255 self.table_next_row_with_flags(TableRowFlags::NONE, 0.0);
256 }
257
258 pub fn table_next_row_with_flags(&self, flags: TableRowFlags, min_row_height: f32) {
260 self.run_with_bound_context(|| unsafe {
261 sys::igTableNextRow(flags.bits(), min_row_height);
262 });
263 }
264
265 #[doc(alias = "TableSetupScrollFreeze")]
267 pub fn table_setup_scroll_freeze(&self, frozen_cols: usize, frozen_rows: usize) {
268 let frozen_cols = table_freeze_count_to_i32(
269 "Ui::table_setup_scroll_freeze()",
270 "frozen_cols",
271 frozen_cols,
272 TABLE_MAX_COLUMNS,
273 );
274 let frozen_rows = table_freeze_count_to_i32(
275 "Ui::table_setup_scroll_freeze()",
276 "frozen_rows",
277 frozen_rows,
278 128,
279 );
280 self.run_with_bound_context(|| {
281 assert_table_setup_phase("Ui::table_setup_scroll_freeze()");
282 unsafe { sys::igTableSetupScrollFreeze(frozen_cols, frozen_rows) }
283 });
284 }
285
286 #[doc(alias = "TableHeader")]
288 pub fn table_header(&self, label: impl AsRef<str>) {
289 let label_ptr = self.scratch_txt(label);
290 self.run_with_bound_context(|| {
291 assert_current_table_cell("Ui::table_header()");
292 unsafe { sys::igTableHeader(label_ptr) }
293 });
294 }
295
296 #[doc(alias = "TableGetColumnCount")]
298 pub fn table_get_column_count(&self) -> usize {
299 usize::try_from(self.run_with_bound_context(|| unsafe { sys::igTableGetColumnCount() }))
300 .expect("Dear ImGui returned a negative table column count")
301 }
302
303 #[doc(alias = "TableGetColumnIndex")]
305 pub fn table_get_column_index(&self) -> Option<TableColumnIndex> {
306 self.run_with_bound_context(|| {
307 current_table_if_any()?;
308 let raw = unsafe { sys::igTableGetColumnIndex() };
309 (raw >= 0).then(|| TableColumnIndex::from_i32(raw, "Ui::table_get_column_index()"))
310 })
311 }
312
313 #[doc(alias = "TableGetRowIndex")]
315 pub fn table_get_row_index(&self) -> Option<TableRowIndex> {
316 self.run_with_bound_context(|| {
317 current_table_if_any()?;
318 let raw = unsafe { sys::igTableGetRowIndex() };
319 (raw >= 0).then(|| TableRowIndex::from_i32(raw, "Ui::table_get_row_index()"))
320 })
321 }
322
323 #[doc(alias = "TableGetColumnName")]
325 pub fn table_get_column_name(&self, column: impl Into<TableColumnRef>) -> &str {
326 let column = column.into();
327 let column_n = match column {
328 TableColumnRef::Current => -1,
329 TableColumnRef::Index(index) => index.into_i32("Ui::table_get_column_name()"),
330 };
331 self.run_with_bound_context(|| {
332 if current_table_if_any().is_some() {
333 resolve_table_column(column, "Ui::table_get_column_name()");
334 }
335 unsafe {
336 let ptr = sys::igTableGetColumnName_Int(column_n);
337 if ptr.is_null() {
338 ""
339 } else {
340 CStr::from_ptr(ptr).to_str().unwrap_or("")
341 }
342 }
343 })
344 }
345
346 #[doc(alias = "TableGetColumnFlags")]
348 pub fn table_get_column_flags(
349 &self,
350 column: impl Into<TableColumnRef>,
351 ) -> TableColumnStateFlags {
352 let column = column.into();
353 let column_n = match column {
354 TableColumnRef::Current => -1,
355 TableColumnRef::Index(index) => index.into_i32("Ui::table_get_column_flags()"),
356 };
357 self.run_with_bound_context(|| {
358 if let Some(table) = current_table_if_any() {
359 let column_count = unsafe { (*table).ColumnsCount };
360 let resolved_column = match column {
361 TableColumnRef::Current => unsafe { (*table).CurrentColumn },
362 TableColumnRef::Index(_) => column_n,
363 };
364 assert!(
365 (0..column_count).contains(&resolved_column),
366 "Ui::table_get_column_flags() column index {resolved_column} is outside the current table column range 0..{column_count}"
367 );
368 }
369 unsafe { TableColumnStateFlags::from_bits_retain(sys::igTableGetColumnFlags(column_n)) }
370 })
371 }
372
373 #[doc(alias = "TableSetColumnEnabled")]
375 pub fn table_set_column_enabled(&self, column: impl Into<TableColumnRef>, enabled: bool) {
376 let column = column.into();
377 let column_n = match column {
378 TableColumnRef::Current => -1,
379 TableColumnRef::Index(index) => index.into_i32("Ui::table_set_column_enabled()"),
380 };
381 self.run_with_bound_context(|| {
382 assert_current_table_has_flags(TableFlags::HIDEABLE, "Ui::table_set_column_enabled()");
383 resolve_table_column(column, "Ui::table_set_column_enabled()");
384 unsafe { sys::igTableSetColumnEnabled(column_n, enabled) }
385 });
386 }
387
388 #[doc(alias = "TableGetHoveredColumn")]
390 pub fn table_get_hovered_column(&self) -> TableHoveredColumn {
391 self.run_with_bound_context(|| {
392 let raw = unsafe { sys::igTableGetHoveredColumn() };
393 if raw < 0 {
394 return TableHoveredColumn::None;
395 }
396 if let Some(table) = current_table_if_any() {
397 let column_count = unsafe { (*table).ColumnsCount };
398 if raw == column_count {
399 return TableHoveredColumn::UnusedSpace;
400 }
401 }
402 TableHoveredColumn::Column(TableColumnIndex::from_i32(
403 raw,
404 "Ui::table_get_hovered_column()",
405 ))
406 })
407 }
408
409 #[doc(alias = "TableSetColumnWidth")]
411 pub fn table_set_column_width(&self, column: impl Into<TableColumnIndex>, width: f32) {
412 assert_non_negative_finite_f32("Ui::table_set_column_width()", "width", width);
413 let column = column.into();
414 self.run_with_bound_context(|| {
415 assert_table_column_width_phase("Ui::table_set_column_width()");
416 let column_n = assert_valid_table_column(column, "Ui::table_set_column_width()");
417 unsafe { sys::igTableSetColumnWidth(column_n, width) }
418 });
419 }
420
421 #[doc(alias = "TableSetBgColor")]
426 pub fn table_set_cell_bg_color_u32(&self, color: u32, column: impl Into<TableColumnRef>) {
427 let column = column.into();
428 let column_n = match column {
429 TableColumnRef::Current => -1,
430 TableColumnRef::Index(index) => index.into_i32("Ui::table_set_cell_bg_color_u32()"),
431 };
432 self.run_with_bound_context(|| {
433 assert_current_table_row("Ui::table_set_cell_bg_color_u32()");
434 resolve_table_column(column, "Ui::table_set_cell_bg_color_u32()");
435 unsafe { sys::igTableSetBgColor(TableBgTarget::CellBg as i32, color, column_n) }
436 });
437 }
438
439 pub fn table_set_cell_bg_color(&self, rgba: [f32; 4], column: impl Into<TableColumnRef>) {
441 let col = crate::colors::Color::from_array(rgba).to_imgui_u32();
442 self.table_set_cell_bg_color_u32(col, column);
443 }
444
445 #[doc(alias = "TableSetBgColor")]
447 pub fn table_set_row_bg0_color_u32(&self, color: u32) {
448 self.run_with_bound_context(|| {
449 assert_current_table_row("Ui::table_set_row_bg0_color_u32()");
450 unsafe { sys::igTableSetBgColor(TableBgTarget::RowBg0 as i32, color, -1) }
451 });
452 }
453
454 pub fn table_set_row_bg0_color(&self, rgba: [f32; 4]) {
456 let col = crate::colors::Color::from_array(rgba).to_imgui_u32();
457 self.table_set_row_bg0_color_u32(col);
458 }
459
460 #[doc(alias = "TableSetBgColor")]
462 pub fn table_set_row_bg1_color_u32(&self, color: u32) {
463 self.run_with_bound_context(|| {
464 assert_current_table_row("Ui::table_set_row_bg1_color_u32()");
465 unsafe { sys::igTableSetBgColor(TableBgTarget::RowBg1 as i32, color, -1) }
466 });
467 }
468
469 pub fn table_set_row_bg1_color(&self, rgba: [f32; 4]) {
471 let col = crate::colors::Color::from_array(rgba).to_imgui_u32();
472 self.table_set_row_bg1_color_u32(col);
473 }
474
475 #[doc(alias = "TableGetHoveredRow")]
477 pub fn table_get_hovered_row(&self) -> TableHoveredRow {
478 self.run_with_bound_context(|| {
479 if current_table_if_any().is_none() {
480 return TableHoveredRow::None;
481 }
482 let raw = unsafe { sys::igTableGetHoveredRow() };
483 if raw < 0 {
484 return TableHoveredRow::None;
485 }
486 TableHoveredRow::Row(TableRowIndex::from_i32(raw, "Ui::table_get_hovered_row()"))
487 })
488 }
489
490 #[doc(alias = "TableGetHeaderRowHeight")]
492 pub fn table_get_header_row_height(&self) -> f32 {
493 self.run_with_bound_context(|| unsafe { sys::igTableGetHeaderRowHeight() })
494 }
495
496 #[doc(alias = "TableSetColumnSortDirection")]
498 pub fn table_set_column_sort_direction(
499 &self,
500 column: impl Into<TableColumnIndex>,
501 dir: SortDirection,
502 append_to_sort_specs: bool,
503 ) {
504 let column = column.into();
505 self.run_with_bound_context(|| unsafe {
506 let column_n =
507 assert_valid_table_column(column, "Ui::table_set_column_sort_direction()");
508 sys::igTableSetColumnSortDirection(column_n, dir.into(), append_to_sort_specs)
509 });
510 }
511
512 #[doc(alias = "TableGetSortSpecs")]
516 pub fn table_get_sort_specs(&self) -> Option<TableSortSpecs<'_>> {
517 self.run_with_bound_context(|| unsafe {
518 let ptr = sys::igTableGetSortSpecs();
519 if ptr.is_null() {
520 None
521 } else {
522 Some(TableSortSpecs::from_raw(ptr))
523 }
524 })
525 }
526}