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};
44#[cfg(feature = "serde")]
45use serde::{Deserialize, Serialize};
46use std::ffi::CStr;
47
48#[derive(Clone, Debug)]
50pub struct TableColumnSetup<Name: AsRef<str>> {
51 pub name: Name,
52 pub flags: TableColumnFlags,
53 pub init_width_or_weight: f32,
54 pub user_id: u32,
55}
56
57impl<Name: AsRef<str>> TableColumnSetup<Name> {
58 pub fn new(name: Name) -> Self {
60 Self {
61 name,
62 flags: TableColumnFlags::NONE,
63 init_width_or_weight: 0.0,
64 user_id: 0,
65 }
66 }
67
68 pub fn flags(mut self, flags: TableColumnFlags) -> Self {
70 self.flags = flags;
71 self
72 }
73
74 pub fn init_width_or_weight(mut self, width: f32) -> Self {
76 self.init_width_or_weight = width;
77 self
78 }
79
80 pub fn user_id(mut self, id: u32) -> Self {
82 self.user_id = id;
83 self
84 }
85}
86
87impl Ui {
89 pub fn table(&self, str_id: impl AsRef<str>) -> TableBuilder<'_> {
110 TableBuilder::new(self, str_id)
111 }
112 #[must_use = "if return is dropped immediately, table is ended immediately."]
118 pub fn begin_table(
119 &self,
120 str_id: impl AsRef<str>,
121 column_count: usize,
122 ) -> Option<TableToken<'_>> {
123 self.begin_table_with_flags(str_id, column_count, TableFlags::NONE)
124 }
125
126 #[must_use = "if return is dropped immediately, table is ended immediately."]
128 pub fn begin_table_with_flags(
129 &self,
130 str_id: impl AsRef<str>,
131 column_count: usize,
132 flags: TableFlags,
133 ) -> Option<TableToken<'_>> {
134 self.begin_table_with_sizing(str_id, column_count, flags, [0.0, 0.0], 0.0)
135 }
136
137 #[must_use = "if return is dropped immediately, table is ended immediately."]
140 pub fn begin_table_with_sizing(
141 &self,
142 str_id: impl AsRef<str>,
143 column_count: usize,
144 flags: TableFlags,
145 outer_size: impl Into<[f32; 2]>,
146 inner_width: f32,
147 ) -> Option<TableToken<'_>> {
148 let str_id_ptr = self.scratch_txt(str_id);
149 let outer_size_vec: sys::ImVec2 = outer_size.into().into();
150
151 let should_render = unsafe {
152 sys::igBeginTable(
153 str_id_ptr,
154 column_count as i32,
155 flags.bits(),
156 outer_size_vec,
157 inner_width,
158 )
159 };
160
161 if should_render {
162 Some(TableToken::new(self))
163 } else {
164 None
165 }
166 }
167
168 #[must_use = "if return is dropped immediately, table is ended immediately."]
173 pub fn begin_table_header<Name: AsRef<str>, const N: usize>(
174 &self,
175 str_id: impl AsRef<str>,
176 column_data: [TableColumnSetup<Name>; N],
177 ) -> Option<TableToken<'_>> {
178 self.begin_table_header_with_flags(str_id, column_data, TableFlags::NONE)
179 }
180
181 #[must_use = "if return is dropped immediately, table is ended immediately."]
186 pub fn begin_table_header_with_flags<Name: AsRef<str>, const N: usize>(
187 &self,
188 str_id: impl AsRef<str>,
189 column_data: [TableColumnSetup<Name>; N],
190 flags: TableFlags,
191 ) -> Option<TableToken<'_>> {
192 if let Some(token) = self.begin_table_with_flags(str_id, N, flags) {
193 for column in &column_data {
195 self.table_setup_column(
196 &column.name,
197 column.flags,
198 column.init_width_or_weight,
199 column.user_id,
200 );
201 }
202 self.table_headers_row();
203 Some(token)
204 } else {
205 None
206 }
207 }
208
209 pub fn table_setup_column(
211 &self,
212 label: impl AsRef<str>,
213 flags: TableColumnFlags,
214 init_width_or_weight: f32,
215 user_id: u32,
216 ) {
217 let label_ptr = self.scratch_txt(label);
218 unsafe {
219 sys::igTableSetupColumn(label_ptr, flags.bits(), init_width_or_weight, user_id);
220 }
221 }
222
223 pub fn table_headers_row(&self) {
225 unsafe {
226 sys::igTableHeadersRow();
227 }
228 }
229
230 pub fn table_next_column(&self) -> bool {
232 unsafe { sys::igTableNextColumn() }
233 }
234
235 pub fn table_set_column_index(&self, column_n: i32) -> bool {
237 unsafe { sys::igTableSetColumnIndex(column_n) }
238 }
239
240 pub fn table_next_row(&self) {
242 self.table_next_row_with_flags(TableRowFlags::NONE, 0.0);
243 }
244
245 pub fn table_next_row_with_flags(&self, flags: TableRowFlags, min_row_height: f32) {
247 unsafe {
248 sys::igTableNextRow(flags.bits(), min_row_height);
249 }
250 }
251
252 #[doc(alias = "TableSetupScrollFreeze")]
254 pub fn table_setup_scroll_freeze(&self, frozen_cols: i32, frozen_rows: i32) {
255 unsafe { sys::igTableSetupScrollFreeze(frozen_cols, frozen_rows) }
256 }
257
258 #[doc(alias = "TableHeader")]
260 pub fn table_header(&self, label: impl AsRef<str>) {
261 let label_ptr = self.scratch_txt(label);
262 unsafe { sys::igTableHeader(label_ptr) }
263 }
264
265 #[doc(alias = "TableGetColumnCount")]
267 pub fn table_get_column_count(&self) -> i32 {
268 unsafe { sys::igTableGetColumnCount() }
269 }
270
271 #[doc(alias = "TableGetColumnIndex")]
273 pub fn table_get_column_index(&self) -> i32 {
274 unsafe { sys::igTableGetColumnIndex() }
275 }
276
277 #[doc(alias = "TableGetRowIndex")]
279 pub fn table_get_row_index(&self) -> i32 {
280 unsafe { sys::igTableGetRowIndex() }
281 }
282
283 #[doc(alias = "TableGetColumnName")]
285 pub fn table_get_column_name(&self, column_n: i32) -> &str {
286 unsafe {
287 let ptr = sys::igTableGetColumnName_Int(column_n);
288 if ptr.is_null() {
289 ""
290 } else {
291 CStr::from_ptr(ptr).to_str().unwrap_or("")
292 }
293 }
294 }
295
296 #[doc(alias = "TableGetColumnFlags")]
298 pub fn table_get_column_flags(&self, column_n: i32) -> TableColumnFlags {
299 unsafe { TableColumnFlags::from_bits_truncate(sys::igTableGetColumnFlags(column_n)) }
300 }
301
302 #[doc(alias = "TableSetColumnEnabled")]
304 pub fn table_set_column_enabled(&self, column_n: i32, enabled: bool) {
305 unsafe { sys::igTableSetColumnEnabled(column_n, enabled) }
306 }
307
308 #[doc(alias = "TableGetHoveredColumn")]
310 pub fn table_get_hovered_column(&self) -> i32 {
311 unsafe { sys::igTableGetHoveredColumn() }
312 }
313
314 #[doc(alias = "TableSetColumnWidth")]
316 pub fn table_set_column_width(&self, column_n: i32, width: f32) {
317 unsafe { sys::igTableSetColumnWidth(column_n, width) }
318 }
319
320 #[doc(alias = "TableSetBgColor")]
325 pub fn table_set_bg_color_u32(&self, target: TableBgTarget, color: u32, column_n: i32) {
326 unsafe { sys::igTableSetBgColor(target as i32, color, column_n) }
327 }
328
329 pub fn table_set_bg_color(&self, target: TableBgTarget, rgba: [f32; 4], column_n: i32) {
331 let col = crate::colors::Color::from_array(rgba).to_imgui_u32();
333 unsafe { sys::igTableSetBgColor(target as i32, col, column_n) }
334 }
335
336 #[doc(alias = "TableGetHoveredRow")]
338 pub fn table_get_hovered_row(&self) -> i32 {
339 unsafe { sys::igTableGetHoveredRow() }
340 }
341
342 #[doc(alias = "TableGetHeaderRowHeight")]
344 pub fn table_get_header_row_height(&self) -> f32 {
345 unsafe { sys::igTableGetHeaderRowHeight() }
346 }
347
348 #[doc(alias = "TableSetColumnSortDirection")]
350 pub fn table_set_column_sort_direction(
351 &self,
352 column_n: i32,
353 dir: SortDirection,
354 append_to_sort_specs: bool,
355 ) {
356 unsafe { sys::igTableSetColumnSortDirection(column_n, dir.into(), append_to_sort_specs) }
357 }
358
359 #[doc(alias = "TableGetSortSpecs")]
363 pub fn table_get_sort_specs(&self) -> Option<TableSortSpecs<'_>> {
364 unsafe {
365 let ptr = sys::igTableGetSortSpecs();
366 if ptr.is_null() {
367 None
368 } else {
369 Some(TableSortSpecs::from_raw(ptr))
370 }
371 }
372 }
373}
374
375bitflags::bitflags! {
376 #[repr(transparent)]
378 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
379 pub struct TableRowFlags: i32 {
380 const NONE = 0;
382 const HEADERS = sys::ImGuiTableRowFlags_Headers as i32;
384 }
385}
386
387#[cfg(feature = "serde")]
388impl Serialize for TableRowFlags {
389 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
390 where
391 S: serde::Serializer,
392 {
393 serializer.serialize_i32(self.bits())
394 }
395}
396
397#[cfg(feature = "serde")]
398impl<'de> Deserialize<'de> for TableRowFlags {
399 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
400 where
401 D: serde::Deserializer<'de>,
402 {
403 let bits = i32::deserialize(deserializer)?;
404 Ok(TableRowFlags::from_bits_truncate(bits))
405 }
406}
407
408#[repr(i32)]
410#[derive(Copy, Clone, Debug, PartialEq, Eq)]
411#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
412pub enum TableBgTarget {
413 None = sys::ImGuiTableBgTarget_None as i32,
415 RowBg0 = sys::ImGuiTableBgTarget_RowBg0 as i32,
417 RowBg1 = sys::ImGuiTableBgTarget_RowBg1 as i32,
419 CellBg = sys::ImGuiTableBgTarget_CellBg as i32,
421}
422
423#[repr(u8)]
425#[derive(Copy, Clone, Debug, PartialEq, Eq)]
426#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
427pub enum SortDirection {
428 None = sys::ImGuiSortDirection_None as u8,
429 Ascending = sys::ImGuiSortDirection_Ascending as u8,
430 Descending = sys::ImGuiSortDirection_Descending as u8,
431}
432
433impl From<SortDirection> for sys::ImGuiSortDirection {
434 #[inline]
435 fn from(value: SortDirection) -> sys::ImGuiSortDirection {
436 match value {
437 SortDirection::None => sys::ImGuiSortDirection_None,
438 SortDirection::Ascending => sys::ImGuiSortDirection_Ascending,
439 SortDirection::Descending => sys::ImGuiSortDirection_Descending,
440 }
441 }
442}
443
444#[derive(Copy, Clone, Debug)]
446pub struct TableColumnSortSpec {
447 pub column_user_id: u32,
448 pub column_index: i16,
449 pub sort_order: i16,
450 pub sort_direction: SortDirection,
451}
452
453pub struct TableSortSpecs<'a> {
455 raw: *mut sys::ImGuiTableSortSpecs,
456 _marker: std::marker::PhantomData<&'a Ui>,
457}
458
459impl<'a> TableSortSpecs<'a> {
460 pub(crate) unsafe fn from_raw(raw: *mut sys::ImGuiTableSortSpecs) -> Self {
463 Self {
464 raw,
465 _marker: std::marker::PhantomData,
466 }
467 }
468
469 pub fn is_dirty(&self) -> bool {
471 unsafe { (*self.raw).SpecsDirty }
472 }
473
474 pub fn clear_dirty(&mut self) {
476 unsafe { (*self.raw).SpecsDirty = false }
477 }
478
479 pub fn len(&self) -> usize {
481 unsafe { (*self.raw).SpecsCount as usize }
482 }
483
484 pub fn is_empty(&self) -> bool {
485 self.len() == 0
486 }
487
488 pub fn iter(&self) -> TableSortSpecsIter<'_> {
490 TableSortSpecsIter {
491 specs: self,
492 index: 0,
493 }
494 }
495}
496
497pub struct TableSortSpecsIter<'a> {
499 specs: &'a TableSortSpecs<'a>,
500 index: usize,
501}
502
503impl<'a> Iterator for TableSortSpecsIter<'a> {
504 type Item = TableColumnSortSpec;
505 fn next(&mut self) -> Option<Self::Item> {
506 if self.index >= self.specs.len() {
507 return None;
508 }
509 unsafe {
510 let ptr = (*self.specs.raw).Specs;
511 if ptr.is_null() {
512 return None;
513 }
514 let spec = &*ptr.add(self.index);
515 self.index += 1;
516 let d = spec.SortDirection as u8;
517 let dir = if d == sys::ImGuiSortDirection_None as u8 {
518 SortDirection::None
519 } else if d == sys::ImGuiSortDirection_Ascending as u8 {
520 SortDirection::Ascending
521 } else if d == sys::ImGuiSortDirection_Descending as u8 {
522 SortDirection::Descending
523 } else {
524 SortDirection::None
525 };
526 Some(TableColumnSortSpec {
527 column_user_id: spec.ColumnUserID,
528 column_index: spec.ColumnIndex,
529 sort_order: spec.SortOrder,
530 sort_direction: dir,
531 })
532 }
533 }
534}
535
536#[must_use]
538pub struct TableToken<'ui> {
539 ui: &'ui Ui,
540}
541
542impl<'ui> TableToken<'ui> {
543 fn new(ui: &'ui Ui) -> Self {
545 TableToken { ui }
546 }
547
548 pub fn end(self) {
550 }
552}
553
554impl<'ui> Drop for TableToken<'ui> {
555 fn drop(&mut self) {
556 unsafe {
557 sys::igEndTable();
558 }
559 }
560}
561
562#[derive(Copy, Clone, Debug, PartialEq)]
568pub struct TableHeaderData {
569 pub index: i16,
570 pub text_color: ImColor32,
571 pub bg_color0: ImColor32,
572 pub bg_color1: ImColor32,
573}
574
575impl TableHeaderData {
576 pub fn new(
577 index: i16,
578 text_color: ImColor32,
579 bg_color0: ImColor32,
580 bg_color1: ImColor32,
581 ) -> Self {
582 Self {
583 index,
584 text_color,
585 bg_color0,
586 bg_color1,
587 }
588 }
589}
590impl Ui {
591 #[doc(alias = "TableGetHeaderAngledMaxLabelWidth")]
593 pub fn table_get_header_angled_max_label_width(&self) -> f32 {
594 unsafe { sys::igTableGetHeaderAngledMaxLabelWidth() }
595 }
596
597 #[doc(alias = "TableAngledHeadersRow")]
599 pub fn table_angled_headers_row(&self) {
600 unsafe { sys::igTableAngledHeadersRow() }
601 }
602
603 pub fn table_angled_headers_row_ex_with_data(
612 &self,
613 row_id: u32,
614 angle: f32,
615 max_label_width: f32,
616 headers: &[TableHeaderData],
617 ) {
618 if headers.is_empty() {
619 unsafe { sys::igTableAngledHeadersRow() }
620 return;
621 }
622 let mut data: Vec<sys::ImGuiTableHeaderData> = Vec::with_capacity(headers.len());
623 for h in headers {
624 data.push(sys::ImGuiTableHeaderData {
625 Index: h.index as sys::ImGuiTableColumnIdx,
626 TextColor: u32::from(h.text_color),
627 BgColor0: u32::from(h.bg_color0),
628 BgColor1: u32::from(h.bg_color1),
629 });
630 }
631 unsafe {
632 sys::igTableAngledHeadersRowEx(
633 row_id,
634 angle,
635 max_label_width,
636 data.as_ptr(),
637 data.len() as i32,
638 );
639 }
640 }
641
642 #[doc(alias = "TablePushBackgroundChannel")]
644 pub fn table_push_background_channel(&self) {
645 unsafe { sys::igTablePushBackgroundChannel() }
646 }
647
648 #[doc(alias = "TablePopBackgroundChannel")]
650 pub fn table_pop_background_channel(&self) {
651 unsafe { sys::igTablePopBackgroundChannel() }
652 }
653
654 #[doc(alias = "TablePushColumnChannel")]
656 pub fn table_push_column_channel(&self, column_n: i32) {
657 unsafe { sys::igTablePushColumnChannel(column_n) }
658 }
659
660 #[doc(alias = "TablePopColumnChannel")]
662 pub fn table_pop_column_channel(&self) {
663 unsafe { sys::igTablePopColumnChannel() }
664 }
665
666 pub fn with_table_background_channel<R>(&self, f: impl FnOnce() -> R) -> R {
668 self.table_push_background_channel();
669 let result = f();
670 self.table_pop_background_channel();
671 result
672 }
673
674 pub fn with_table_column_channel<R>(&self, column_n: i32, f: impl FnOnce() -> R) -> R {
676 self.table_push_column_channel(column_n);
677 let result = f();
678 self.table_pop_column_channel();
679 result
680 }
681
682 #[doc(alias = "TableOpenContextMenu")]
684 pub fn table_open_context_menu(&self, column_n: Option<i32>) {
685 unsafe { sys::igTableOpenContextMenu(column_n.unwrap_or(-1)) }
686 }
687}
688
689#[derive(Debug)]
697pub struct TableBuilder<'ui> {
698 ui: &'ui Ui,
699 id: String,
700 flags: TableFlags,
701 outer_size: [f32; 2],
702 inner_width: f32,
703 columns: Vec<TableColumnSetup<String>>, use_headers: bool,
705 freeze: Option<(i32, i32)>,
706}
707
708impl<'ui> TableBuilder<'ui> {
709 pub fn new(ui: &'ui Ui, str_id: impl AsRef<str>) -> Self {
711 Self {
712 ui,
713 id: str_id.as_ref().to_string(),
714 flags: TableFlags::NONE,
715 outer_size: [0.0, 0.0],
716 inner_width: 0.0,
717 columns: Vec::new(),
718 use_headers: false,
719 freeze: None,
720 }
721 }
722
723 pub fn flags(mut self, flags: TableFlags) -> Self {
725 self.flags = flags;
726 self
727 }
728
729 pub fn outer_size(mut self, size: [f32; 2]) -> Self {
731 self.outer_size = size;
732 self
733 }
734
735 pub fn inner_width(mut self, width: f32) -> Self {
737 self.inner_width = width;
738 self
739 }
740
741 pub fn freeze(mut self, frozen_cols: i32, frozen_rows: i32) -> Self {
743 self.freeze = Some((frozen_cols, frozen_rows));
744 self
745 }
746
747 pub fn column(self, name: impl AsRef<str>) -> ColumnBuilder<'ui> {
750 ColumnBuilder::new(self, name)
751 }
752
753 pub fn columns<Name: AsRef<str>>(
755 mut self,
756 cols: impl IntoIterator<Item = TableColumnSetup<Name>>,
757 ) -> Self {
758 self.columns.clear();
759 for c in cols {
760 self.columns.push(TableColumnSetup {
761 name: c.name.as_ref().to_string(),
762 flags: c.flags,
763 init_width_or_weight: c.init_width_or_weight,
764 user_id: c.user_id,
765 });
766 }
767 self
768 }
769
770 pub fn add_column<Name: AsRef<str>>(mut self, col: TableColumnSetup<Name>) -> Self {
772 self.columns.push(TableColumnSetup {
773 name: col.name.as_ref().to_string(),
774 flags: col.flags,
775 init_width_or_weight: col.init_width_or_weight,
776 user_id: col.user_id,
777 });
778 self
779 }
780
781 pub fn headers(mut self, enabled: bool) -> Self {
783 self.use_headers = enabled;
784 self
785 }
786
787 pub fn build(self, f: impl FnOnce(&Ui)) {
789 let Some(token) = self.ui.begin_table_with_sizing(
790 &self.id,
791 self.columns.len(),
792 self.flags,
793 self.outer_size,
794 self.inner_width,
795 ) else {
796 return;
797 };
798
799 if let Some((fc, fr)) = self.freeze {
800 self.ui.table_setup_scroll_freeze(fc, fr);
801 }
802
803 if !self.columns.is_empty() {
804 for col in &self.columns {
805 self.ui.table_setup_column(
806 &col.name,
807 col.flags,
808 col.init_width_or_weight,
809 col.user_id,
810 );
811 }
812 if self.use_headers {
813 self.ui.table_headers_row();
814 }
815 }
816
817 f(self.ui);
818
819 token.end();
821 }
822}
823
824#[derive(Debug)]
826pub struct ColumnBuilder<'ui> {
827 parent: TableBuilder<'ui>,
828 name: String,
829 flags: TableColumnFlags,
830 init_width_or_weight: f32,
831 user_id: u32,
832}
833
834impl<'ui> ColumnBuilder<'ui> {
835 fn new(parent: TableBuilder<'ui>, name: impl AsRef<str>) -> Self {
836 Self {
837 parent,
838 name: name.as_ref().to_string(),
839 flags: TableColumnFlags::NONE,
840 init_width_or_weight: 0.0,
841 user_id: 0,
842 }
843 }
844
845 pub fn flags(mut self, flags: TableColumnFlags) -> Self {
847 self.flags = flags;
848 self
849 }
850
851 pub fn width(mut self, width: f32) -> Self {
853 self.init_width_or_weight = width;
854 self
855 }
856
857 pub fn weight(self, weight: f32) -> Self {
859 self.width(weight)
860 }
861
862 pub fn angled_header(mut self, enabled: bool) -> Self {
864 if enabled {
865 self.flags.insert(TableColumnFlags::ANGLED_HEADER);
866 } else {
867 self.flags.remove(TableColumnFlags::ANGLED_HEADER);
868 }
869 self
870 }
871
872 pub fn user_id(mut self, id: u32) -> Self {
874 self.user_id = id;
875 self
876 }
877
878 pub fn done(mut self) -> TableBuilder<'ui> {
880 self.parent.columns.push(TableColumnSetup {
881 name: self.name,
882 flags: self.flags,
883 init_width_or_weight: self.init_width_or_weight,
884 user_id: self.user_id,
885 });
886 self.parent
887 }
888}