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