1#![allow(
36 clippy::cast_possible_truncation,
37 clippy::cast_sign_loss,
38 clippy::as_conversions
39)]
40use crate::draw::ImColor32;
41use crate::sys;
42use crate::ui::Ui;
43use crate::widget::{TableColumnFlags, TableFlags};
44use std::ffi::CStr;
45
46#[derive(Clone, Debug)]
48pub struct TableColumnSetup<Name: AsRef<str>> {
49 pub name: Name,
50 pub flags: TableColumnFlags,
51 pub init_width_or_weight: f32,
52 pub user_id: u32,
53}
54
55impl<Name: AsRef<str>> TableColumnSetup<Name> {
56 pub fn new(name: Name) -> Self {
58 Self {
59 name,
60 flags: TableColumnFlags::NONE,
61 init_width_or_weight: 0.0,
62 user_id: 0,
63 }
64 }
65
66 pub fn flags(mut self, flags: TableColumnFlags) -> Self {
68 self.flags = flags;
69 self
70 }
71
72 pub fn init_width_or_weight(mut self, width: f32) -> Self {
74 self.init_width_or_weight = width;
75 self
76 }
77
78 pub fn user_id(mut self, id: u32) -> Self {
80 self.user_id = id;
81 self
82 }
83}
84
85impl Ui {
87 pub fn table(&self, str_id: impl AsRef<str>) -> TableBuilder<'_> {
108 TableBuilder::new(self, str_id)
109 }
110 #[must_use = "if return is dropped immediately, table is ended immediately."]
116 pub fn begin_table(
117 &self,
118 str_id: impl AsRef<str>,
119 column_count: usize,
120 ) -> Option<TableToken<'_>> {
121 self.begin_table_with_flags(str_id, column_count, TableFlags::NONE)
122 }
123
124 #[must_use = "if return is dropped immediately, table is ended immediately."]
126 pub fn begin_table_with_flags(
127 &self,
128 str_id: impl AsRef<str>,
129 column_count: usize,
130 flags: TableFlags,
131 ) -> Option<TableToken<'_>> {
132 self.begin_table_with_sizing(str_id, column_count, flags, [0.0, 0.0], 0.0)
133 }
134
135 #[must_use = "if return is dropped immediately, table is ended immediately."]
138 pub fn begin_table_with_sizing(
139 &self,
140 str_id: impl AsRef<str>,
141 column_count: usize,
142 flags: TableFlags,
143 outer_size: impl Into<[f32; 2]>,
144 inner_width: f32,
145 ) -> Option<TableToken<'_>> {
146 let str_id_ptr = self.scratch_txt(str_id);
147 let outer_size_vec: sys::ImVec2 = outer_size.into().into();
148
149 let should_render = unsafe {
150 sys::igBeginTable(
151 str_id_ptr,
152 column_count as i32,
153 flags.bits(),
154 outer_size_vec,
155 inner_width,
156 )
157 };
158
159 if should_render {
160 Some(TableToken::new(self))
161 } else {
162 None
163 }
164 }
165
166 #[must_use = "if return is dropped immediately, table is ended immediately."]
171 pub fn begin_table_header<Name: AsRef<str>, const N: usize>(
172 &self,
173 str_id: impl AsRef<str>,
174 column_data: [TableColumnSetup<Name>; N],
175 ) -> Option<TableToken<'_>> {
176 self.begin_table_header_with_flags(str_id, column_data, TableFlags::NONE)
177 }
178
179 #[must_use = "if return is dropped immediately, table is ended immediately."]
184 pub fn begin_table_header_with_flags<Name: AsRef<str>, const N: usize>(
185 &self,
186 str_id: impl AsRef<str>,
187 column_data: [TableColumnSetup<Name>; N],
188 flags: TableFlags,
189 ) -> Option<TableToken<'_>> {
190 if let Some(token) = self.begin_table_with_flags(str_id, N, flags) {
191 for column in &column_data {
193 self.table_setup_column(
194 &column.name,
195 column.flags,
196 column.init_width_or_weight,
197 column.user_id,
198 );
199 }
200 self.table_headers_row();
201 Some(token)
202 } else {
203 None
204 }
205 }
206
207 pub fn table_setup_column(
209 &self,
210 label: impl AsRef<str>,
211 flags: TableColumnFlags,
212 init_width_or_weight: f32,
213 user_id: u32,
214 ) {
215 let label_ptr = self.scratch_txt(label);
216 unsafe {
217 sys::igTableSetupColumn(label_ptr, flags.bits(), init_width_or_weight, user_id);
218 }
219 }
220
221 pub fn table_headers_row(&self) {
223 unsafe {
224 sys::igTableHeadersRow();
225 }
226 }
227
228 pub fn table_next_column(&self) -> bool {
230 unsafe { sys::igTableNextColumn() }
231 }
232
233 pub fn table_set_column_index(&self, column_n: i32) -> bool {
235 unsafe { sys::igTableSetColumnIndex(column_n) }
236 }
237
238 pub fn table_next_row(&self) {
240 self.table_next_row_with_flags(TableRowFlags::NONE, 0.0);
241 }
242
243 pub fn table_next_row_with_flags(&self, flags: TableRowFlags, min_row_height: f32) {
245 unsafe {
246 sys::igTableNextRow(flags.bits(), min_row_height);
247 }
248 }
249
250 #[doc(alias = "TableSetupScrollFreeze")]
252 pub fn table_setup_scroll_freeze(&self, frozen_cols: i32, frozen_rows: i32) {
253 unsafe { sys::igTableSetupScrollFreeze(frozen_cols, frozen_rows) }
254 }
255
256 #[doc(alias = "TableHeader")]
258 pub fn table_header(&self, label: impl AsRef<str>) {
259 let label_ptr = self.scratch_txt(label);
260 unsafe { sys::igTableHeader(label_ptr) }
261 }
262
263 #[doc(alias = "TableGetColumnCount")]
265 pub fn table_get_column_count(&self) -> i32 {
266 unsafe { sys::igTableGetColumnCount() }
267 }
268
269 #[doc(alias = "TableGetColumnIndex")]
271 pub fn table_get_column_index(&self) -> i32 {
272 unsafe { sys::igTableGetColumnIndex() }
273 }
274
275 #[doc(alias = "TableGetRowIndex")]
277 pub fn table_get_row_index(&self) -> i32 {
278 unsafe { sys::igTableGetRowIndex() }
279 }
280
281 #[doc(alias = "TableGetColumnName")]
283 pub fn table_get_column_name(&self, column_n: i32) -> &str {
284 unsafe {
285 let ptr = sys::igTableGetColumnName_Int(column_n);
286 if ptr.is_null() {
287 ""
288 } else {
289 CStr::from_ptr(ptr).to_str().unwrap_or("")
290 }
291 }
292 }
293
294 #[doc(alias = "TableGetColumnFlags")]
296 pub fn table_get_column_flags(&self, column_n: i32) -> TableColumnFlags {
297 unsafe { TableColumnFlags::from_bits_truncate(sys::igTableGetColumnFlags(column_n)) }
298 }
299
300 #[doc(alias = "TableSetColumnEnabled")]
302 pub fn table_set_column_enabled(&self, column_n: i32, enabled: bool) {
303 unsafe { sys::igTableSetColumnEnabled(column_n, enabled) }
304 }
305
306 #[doc(alias = "TableGetHoveredColumn")]
308 pub fn table_get_hovered_column(&self) -> i32 {
309 unsafe { sys::igTableGetHoveredColumn() }
310 }
311
312 #[doc(alias = "TableSetColumnWidth")]
314 pub fn table_set_column_width(&self, column_n: i32, width: f32) {
315 unsafe { sys::igTableSetColumnWidth(column_n, width) }
316 }
317
318 #[doc(alias = "TableSetBgColor")]
323 pub fn table_set_bg_color_u32(&self, target: TableBgTarget, color: u32, column_n: i32) {
324 unsafe { sys::igTableSetBgColor(target as i32, color, column_n) }
325 }
326
327 pub fn table_set_bg_color(&self, target: TableBgTarget, rgba: [f32; 4], column_n: i32) {
329 let col = crate::colors::Color::from_array(rgba).to_imgui_u32();
331 unsafe { sys::igTableSetBgColor(target as i32, col, column_n) }
332 }
333
334 #[doc(alias = "TableGetHoveredRow")]
336 pub fn table_get_hovered_row(&self) -> i32 {
337 unsafe { sys::igTableGetHoveredRow() }
338 }
339
340 #[doc(alias = "TableGetHeaderRowHeight")]
342 pub fn table_get_header_row_height(&self) -> f32 {
343 unsafe { sys::igTableGetHeaderRowHeight() }
344 }
345
346 #[doc(alias = "TableSetColumnSortDirection")]
348 pub fn table_set_column_sort_direction(
349 &self,
350 column_n: i32,
351 dir: SortDirection,
352 append_to_sort_specs: bool,
353 ) {
354 unsafe { sys::igTableSetColumnSortDirection(column_n, dir.into(), append_to_sort_specs) }
355 }
356
357 #[doc(alias = "TableGetSortSpecs")]
361 pub fn table_get_sort_specs(&self) -> Option<TableSortSpecs<'_>> {
362 unsafe {
363 let ptr = sys::igTableGetSortSpecs();
364 if ptr.is_null() {
365 None
366 } else {
367 Some(TableSortSpecs::from_raw(ptr))
368 }
369 }
370 }
371}
372
373bitflags::bitflags! {
374 #[repr(transparent)]
376 pub struct TableRowFlags: i32 {
377 const NONE = 0;
379 const HEADERS = sys::ImGuiTableRowFlags_Headers as i32;
381 }
382}
383
384#[repr(i32)]
386#[derive(Copy, Clone, Debug, PartialEq, Eq)]
387pub enum TableBgTarget {
388 None = sys::ImGuiTableBgTarget_None as i32,
390 RowBg0 = sys::ImGuiTableBgTarget_RowBg0 as i32,
392 RowBg1 = sys::ImGuiTableBgTarget_RowBg1 as i32,
394 CellBg = sys::ImGuiTableBgTarget_CellBg as i32,
396}
397
398#[repr(u8)]
400#[derive(Copy, Clone, Debug, PartialEq, Eq)]
401pub enum SortDirection {
402 None = sys::ImGuiSortDirection_None as u8,
403 Ascending = sys::ImGuiSortDirection_Ascending as u8,
404 Descending = sys::ImGuiSortDirection_Descending as u8,
405}
406
407impl From<SortDirection> for sys::ImGuiSortDirection {
408 #[inline]
409 fn from(value: SortDirection) -> sys::ImGuiSortDirection {
410 match value {
411 SortDirection::None => sys::ImGuiSortDirection_None,
412 SortDirection::Ascending => sys::ImGuiSortDirection_Ascending,
413 SortDirection::Descending => sys::ImGuiSortDirection_Descending,
414 }
415 }
416}
417
418#[derive(Copy, Clone, Debug)]
420pub struct TableColumnSortSpec {
421 pub column_user_id: u32,
422 pub column_index: i16,
423 pub sort_order: i16,
424 pub sort_direction: SortDirection,
425}
426
427pub struct TableSortSpecs<'a> {
429 raw: *mut sys::ImGuiTableSortSpecs,
430 _marker: std::marker::PhantomData<&'a Ui>,
431}
432
433impl<'a> TableSortSpecs<'a> {
434 pub(crate) unsafe fn from_raw(raw: *mut sys::ImGuiTableSortSpecs) -> Self {
437 Self {
438 raw,
439 _marker: std::marker::PhantomData,
440 }
441 }
442
443 pub fn is_dirty(&self) -> bool {
445 unsafe { (*self.raw).SpecsDirty }
446 }
447
448 pub fn clear_dirty(&mut self) {
450 unsafe { (*self.raw).SpecsDirty = false }
451 }
452
453 pub fn len(&self) -> usize {
455 unsafe { (*self.raw).SpecsCount as usize }
456 }
457
458 pub fn is_empty(&self) -> bool {
459 self.len() == 0
460 }
461
462 pub fn iter(&self) -> TableSortSpecsIter<'_> {
464 TableSortSpecsIter {
465 specs: self,
466 index: 0,
467 }
468 }
469}
470
471pub struct TableSortSpecsIter<'a> {
473 specs: &'a TableSortSpecs<'a>,
474 index: usize,
475}
476
477impl<'a> Iterator for TableSortSpecsIter<'a> {
478 type Item = TableColumnSortSpec;
479 fn next(&mut self) -> Option<Self::Item> {
480 if self.index >= self.specs.len() {
481 return None;
482 }
483 unsafe {
484 let ptr = (*self.specs.raw).Specs;
485 if ptr.is_null() {
486 return None;
487 }
488 let spec = &*ptr.add(self.index);
489 self.index += 1;
490 let d = spec.SortDirection as u8;
491 let dir = if d == sys::ImGuiSortDirection_None as u8 {
492 SortDirection::None
493 } else if d == sys::ImGuiSortDirection_Ascending as u8 {
494 SortDirection::Ascending
495 } else if d == sys::ImGuiSortDirection_Descending as u8 {
496 SortDirection::Descending
497 } else {
498 SortDirection::None
499 };
500 Some(TableColumnSortSpec {
501 column_user_id: spec.ColumnUserID,
502 column_index: spec.ColumnIndex,
503 sort_order: spec.SortOrder,
504 sort_direction: dir,
505 })
506 }
507 }
508}
509
510#[must_use]
512pub struct TableToken<'ui> {
513 ui: &'ui Ui,
514}
515
516impl<'ui> TableToken<'ui> {
517 fn new(ui: &'ui Ui) -> Self {
519 TableToken { ui }
520 }
521
522 pub fn end(self) {
524 }
526}
527
528impl<'ui> Drop for TableToken<'ui> {
529 fn drop(&mut self) {
530 unsafe {
531 sys::igEndTable();
532 }
533 }
534}
535
536#[derive(Copy, Clone, Debug, PartialEq)]
542pub struct TableHeaderData {
543 pub index: i16,
544 pub text_color: ImColor32,
545 pub bg_color0: ImColor32,
546 pub bg_color1: ImColor32,
547}
548
549impl TableHeaderData {
550 pub fn new(
551 index: i16,
552 text_color: ImColor32,
553 bg_color0: ImColor32,
554 bg_color1: ImColor32,
555 ) -> Self {
556 Self {
557 index,
558 text_color,
559 bg_color0,
560 bg_color1,
561 }
562 }
563}
564impl Ui {
565 #[doc(alias = "TableGetHeaderAngledMaxLabelWidth")]
567 pub fn table_get_header_angled_max_label_width(&self) -> f32 {
568 unsafe { sys::igTableGetHeaderAngledMaxLabelWidth() }
569 }
570
571 #[doc(alias = "TableAngledHeadersRow")]
573 pub fn table_angled_headers_row(&self) {
574 unsafe { sys::igTableAngledHeadersRow() }
575 }
576
577 pub fn table_angled_headers_row_ex_with_data(
586 &self,
587 row_id: u32,
588 angle: f32,
589 max_label_width: f32,
590 headers: &[TableHeaderData],
591 ) {
592 if headers.is_empty() {
593 unsafe { sys::igTableAngledHeadersRow() }
594 return;
595 }
596 let mut data: Vec<sys::ImGuiTableHeaderData> = Vec::with_capacity(headers.len());
597 for h in headers {
598 data.push(sys::ImGuiTableHeaderData {
599 Index: h.index as sys::ImGuiTableColumnIdx,
600 TextColor: u32::from(h.text_color),
601 BgColor0: u32::from(h.bg_color0),
602 BgColor1: u32::from(h.bg_color1),
603 });
604 }
605 unsafe {
606 sys::igTableAngledHeadersRowEx(
607 row_id,
608 angle,
609 max_label_width,
610 data.as_ptr(),
611 data.len() as i32,
612 );
613 }
614 }
615
616 #[doc(alias = "TablePushBackgroundChannel")]
618 pub fn table_push_background_channel(&self) {
619 unsafe { sys::igTablePushBackgroundChannel() }
620 }
621
622 #[doc(alias = "TablePopBackgroundChannel")]
624 pub fn table_pop_background_channel(&self) {
625 unsafe { sys::igTablePopBackgroundChannel() }
626 }
627
628 #[doc(alias = "TablePushColumnChannel")]
630 pub fn table_push_column_channel(&self, column_n: i32) {
631 unsafe { sys::igTablePushColumnChannel(column_n) }
632 }
633
634 #[doc(alias = "TablePopColumnChannel")]
636 pub fn table_pop_column_channel(&self) {
637 unsafe { sys::igTablePopColumnChannel() }
638 }
639
640 pub fn with_table_background_channel<R>(&self, f: impl FnOnce() -> R) -> R {
642 self.table_push_background_channel();
643 let result = f();
644 self.table_pop_background_channel();
645 result
646 }
647
648 pub fn with_table_column_channel<R>(&self, column_n: i32, f: impl FnOnce() -> R) -> R {
650 self.table_push_column_channel(column_n);
651 let result = f();
652 self.table_pop_column_channel();
653 result
654 }
655
656 #[doc(alias = "TableOpenContextMenu")]
658 pub fn table_open_context_menu(&self, column_n: Option<i32>) {
659 unsafe { sys::igTableOpenContextMenu(column_n.unwrap_or(-1)) }
660 }
661}
662
663#[derive(Debug)]
671pub struct TableBuilder<'ui> {
672 ui: &'ui Ui,
673 id: String,
674 flags: TableFlags,
675 outer_size: [f32; 2],
676 inner_width: f32,
677 columns: Vec<TableColumnSetup<String>>, use_headers: bool,
679 freeze: Option<(i32, i32)>,
680}
681
682impl<'ui> TableBuilder<'ui> {
683 pub fn new(ui: &'ui Ui, str_id: impl AsRef<str>) -> Self {
685 Self {
686 ui,
687 id: str_id.as_ref().to_string(),
688 flags: TableFlags::NONE,
689 outer_size: [0.0, 0.0],
690 inner_width: 0.0,
691 columns: Vec::new(),
692 use_headers: false,
693 freeze: None,
694 }
695 }
696
697 pub fn flags(mut self, flags: TableFlags) -> Self {
699 self.flags = flags;
700 self
701 }
702
703 pub fn outer_size(mut self, size: [f32; 2]) -> Self {
705 self.outer_size = size;
706 self
707 }
708
709 pub fn inner_width(mut self, width: f32) -> Self {
711 self.inner_width = width;
712 self
713 }
714
715 pub fn freeze(mut self, frozen_cols: i32, frozen_rows: i32) -> Self {
717 self.freeze = Some((frozen_cols, frozen_rows));
718 self
719 }
720
721 pub fn column(self, name: impl AsRef<str>) -> ColumnBuilder<'ui> {
724 ColumnBuilder::new(self, name)
725 }
726
727 pub fn columns<Name: AsRef<str>>(
729 mut self,
730 cols: impl IntoIterator<Item = TableColumnSetup<Name>>,
731 ) -> Self {
732 self.columns.clear();
733 for c in cols {
734 self.columns.push(TableColumnSetup {
735 name: c.name.as_ref().to_string(),
736 flags: c.flags,
737 init_width_or_weight: c.init_width_or_weight,
738 user_id: c.user_id,
739 });
740 }
741 self
742 }
743
744 pub fn add_column<Name: AsRef<str>>(mut self, col: TableColumnSetup<Name>) -> Self {
746 self.columns.push(TableColumnSetup {
747 name: col.name.as_ref().to_string(),
748 flags: col.flags,
749 init_width_or_weight: col.init_width_or_weight,
750 user_id: col.user_id,
751 });
752 self
753 }
754
755 pub fn headers(mut self, enabled: bool) -> Self {
757 self.use_headers = enabled;
758 self
759 }
760
761 pub fn build(self, f: impl FnOnce(&Ui)) {
763 let Some(token) = self.ui.begin_table_with_sizing(
764 &self.id,
765 self.columns.len(),
766 self.flags,
767 self.outer_size,
768 self.inner_width,
769 ) else {
770 return;
771 };
772
773 if let Some((fc, fr)) = self.freeze {
774 self.ui.table_setup_scroll_freeze(fc, fr);
775 }
776
777 if !self.columns.is_empty() {
778 for col in &self.columns {
779 self.ui.table_setup_column(
780 &col.name,
781 col.flags,
782 col.init_width_or_weight,
783 col.user_id,
784 );
785 }
786 if self.use_headers {
787 self.ui.table_headers_row();
788 }
789 }
790
791 f(self.ui);
792
793 token.end();
795 }
796}
797
798#[derive(Debug)]
800pub struct ColumnBuilder<'ui> {
801 parent: TableBuilder<'ui>,
802 name: String,
803 flags: TableColumnFlags,
804 init_width_or_weight: f32,
805 user_id: u32,
806}
807
808impl<'ui> ColumnBuilder<'ui> {
809 fn new(parent: TableBuilder<'ui>, name: impl AsRef<str>) -> Self {
810 Self {
811 parent,
812 name: name.as_ref().to_string(),
813 flags: TableColumnFlags::NONE,
814 init_width_or_weight: 0.0,
815 user_id: 0,
816 }
817 }
818
819 pub fn flags(mut self, flags: TableColumnFlags) -> Self {
821 self.flags = flags;
822 self
823 }
824
825 pub fn width(mut self, width: f32) -> Self {
827 self.init_width_or_weight = width;
828 self
829 }
830
831 pub fn weight(self, weight: f32) -> Self {
833 self.width(weight)
834 }
835
836 pub fn angled_header(mut self, enabled: bool) -> Self {
838 if enabled {
839 self.flags.insert(TableColumnFlags::ANGLED_HEADER);
840 } else {
841 self.flags.remove(TableColumnFlags::ANGLED_HEADER);
842 }
843 self
844 }
845
846 pub fn user_id(mut self, id: u32) -> Self {
848 self.user_id = id;
849 self
850 }
851
852 pub fn done(mut self) -> TableBuilder<'ui> {
854 self.parent.columns.push(TableColumnSetup {
855 name: self.name,
856 flags: self.flags,
857 init_width_or_weight: self.init_width_or_weight,
858 user_id: self.user_id,
859 });
860 self.parent
861 }
862}