1#![allow(
2 clippy::cast_possible_truncation,
3 clippy::cast_sign_loss,
4 clippy::as_conversions
5)]
6use crate::draw::ImColor32;
7use crate::sys;
8use crate::ui::Ui;
9use crate::widget::{TableColumnFlags, TableFlags};
10use std::ffi::CStr;
11
12#[derive(Clone, Debug)]
14pub struct TableColumnSetup<Name: AsRef<str>> {
15 pub name: Name,
16 pub flags: TableColumnFlags,
17 pub init_width_or_weight: f32,
18 pub user_id: u32,
19}
20
21impl<Name: AsRef<str>> TableColumnSetup<Name> {
22 pub fn new(name: Name) -> Self {
24 Self {
25 name,
26 flags: TableColumnFlags::NONE,
27 init_width_or_weight: 0.0,
28 user_id: 0,
29 }
30 }
31
32 pub fn flags(mut self, flags: TableColumnFlags) -> Self {
34 self.flags = flags;
35 self
36 }
37
38 pub fn init_width_or_weight(mut self, width: f32) -> Self {
40 self.init_width_or_weight = width;
41 self
42 }
43
44 pub fn user_id(mut self, id: u32) -> Self {
46 self.user_id = id;
47 self
48 }
49}
50
51impl Ui {
53 pub fn table(&self, str_id: impl AsRef<str>) -> TableBuilder<'_> {
74 TableBuilder::new(self, str_id)
75 }
76 #[must_use = "if return is dropped immediately, table is ended immediately."]
82 pub fn begin_table(
83 &self,
84 str_id: impl AsRef<str>,
85 column_count: usize,
86 ) -> Option<TableToken<'_>> {
87 self.begin_table_with_flags(str_id, column_count, TableFlags::NONE)
88 }
89
90 #[must_use = "if return is dropped immediately, table is ended immediately."]
92 pub fn begin_table_with_flags(
93 &self,
94 str_id: impl AsRef<str>,
95 column_count: usize,
96 flags: TableFlags,
97 ) -> Option<TableToken<'_>> {
98 self.begin_table_with_sizing(str_id, column_count, flags, [0.0, 0.0], 0.0)
99 }
100
101 #[must_use = "if return is dropped immediately, table is ended immediately."]
104 pub fn begin_table_with_sizing(
105 &self,
106 str_id: impl AsRef<str>,
107 column_count: usize,
108 flags: TableFlags,
109 outer_size: impl Into<[f32; 2]>,
110 inner_width: f32,
111 ) -> Option<TableToken<'_>> {
112 let str_id_ptr = self.scratch_txt(str_id);
113 let outer_size_vec: sys::ImVec2 = outer_size.into().into();
114
115 let should_render = unsafe {
116 sys::igBeginTable(
117 str_id_ptr,
118 column_count as i32,
119 flags.bits(),
120 outer_size_vec,
121 inner_width,
122 )
123 };
124
125 if should_render {
126 Some(TableToken::new(self))
127 } else {
128 None
129 }
130 }
131
132 #[must_use = "if return is dropped immediately, table is ended immediately."]
137 pub fn begin_table_header<Name: AsRef<str>, const N: usize>(
138 &self,
139 str_id: impl AsRef<str>,
140 column_data: [TableColumnSetup<Name>; N],
141 ) -> Option<TableToken<'_>> {
142 self.begin_table_header_with_flags(str_id, column_data, TableFlags::NONE)
143 }
144
145 #[must_use = "if return is dropped immediately, table is ended immediately."]
150 pub fn begin_table_header_with_flags<Name: AsRef<str>, const N: usize>(
151 &self,
152 str_id: impl AsRef<str>,
153 column_data: [TableColumnSetup<Name>; N],
154 flags: TableFlags,
155 ) -> Option<TableToken<'_>> {
156 if let Some(token) = self.begin_table_with_flags(str_id, N, flags) {
157 for column in &column_data {
159 self.table_setup_column(
160 &column.name,
161 column.flags,
162 column.init_width_or_weight,
163 column.user_id,
164 );
165 }
166 self.table_headers_row();
167 Some(token)
168 } else {
169 None
170 }
171 }
172
173 pub fn table_setup_column(
175 &self,
176 label: impl AsRef<str>,
177 flags: TableColumnFlags,
178 init_width_or_weight: f32,
179 user_id: u32,
180 ) {
181 let label_ptr = self.scratch_txt(label);
182 unsafe {
183 sys::igTableSetupColumn(label_ptr, flags.bits(), init_width_or_weight, user_id);
184 }
185 }
186
187 pub fn table_headers_row(&self) {
189 unsafe {
190 sys::igTableHeadersRow();
191 }
192 }
193
194 pub fn table_next_column(&self) -> bool {
196 unsafe { sys::igTableNextColumn() }
197 }
198
199 pub fn table_set_column_index(&self, column_n: i32) -> bool {
201 unsafe { sys::igTableSetColumnIndex(column_n) }
202 }
203
204 pub fn table_next_row(&self) {
206 self.table_next_row_with_flags(TableRowFlags::NONE, 0.0);
207 }
208
209 pub fn table_next_row_with_flags(&self, flags: TableRowFlags, min_row_height: f32) {
211 unsafe {
212 sys::igTableNextRow(flags.bits(), min_row_height);
213 }
214 }
215
216 #[doc(alias = "TableSetupScrollFreeze")]
218 pub fn table_setup_scroll_freeze(&self, frozen_cols: i32, frozen_rows: i32) {
219 unsafe { sys::igTableSetupScrollFreeze(frozen_cols, frozen_rows) }
220 }
221
222 #[doc(alias = "TableHeader")]
224 pub fn table_header(&self, label: impl AsRef<str>) {
225 let label_ptr = self.scratch_txt(label);
226 unsafe { sys::igTableHeader(label_ptr) }
227 }
228
229 #[doc(alias = "TableGetColumnCount")]
231 pub fn table_get_column_count(&self) -> i32 {
232 unsafe { sys::igTableGetColumnCount() }
233 }
234
235 #[doc(alias = "TableGetColumnIndex")]
237 pub fn table_get_column_index(&self) -> i32 {
238 unsafe { sys::igTableGetColumnIndex() }
239 }
240
241 #[doc(alias = "TableGetRowIndex")]
243 pub fn table_get_row_index(&self) -> i32 {
244 unsafe { sys::igTableGetRowIndex() }
245 }
246
247 #[doc(alias = "TableGetColumnName")]
249 pub fn table_get_column_name(&self, column_n: i32) -> &str {
250 unsafe {
251 let ptr = sys::igTableGetColumnName_Int(column_n);
252 if ptr.is_null() {
253 ""
254 } else {
255 CStr::from_ptr(ptr).to_str().unwrap_or("")
256 }
257 }
258 }
259
260 #[doc(alias = "TableGetColumnFlags")]
262 pub fn table_get_column_flags(&self, column_n: i32) -> TableColumnFlags {
263 unsafe { TableColumnFlags::from_bits_truncate(sys::igTableGetColumnFlags(column_n)) }
264 }
265
266 #[doc(alias = "TableSetColumnEnabled")]
268 pub fn table_set_column_enabled(&self, column_n: i32, enabled: bool) {
269 unsafe { sys::igTableSetColumnEnabled(column_n, enabled) }
270 }
271
272 #[doc(alias = "TableGetHoveredColumn")]
274 pub fn table_get_hovered_column(&self) -> i32 {
275 unsafe { sys::igTableGetHoveredColumn() }
276 }
277
278 #[doc(alias = "TableSetColumnWidth")]
280 pub fn table_set_column_width(&self, column_n: i32, width: f32) {
281 unsafe { sys::igTableSetColumnWidth(column_n, width) }
282 }
283
284 #[doc(alias = "TableSetBgColor")]
289 pub fn table_set_bg_color_u32(&self, target: TableBgTarget, color: u32, column_n: i32) {
290 unsafe { sys::igTableSetBgColor(target as i32, color, column_n) }
291 }
292
293 pub fn table_set_bg_color(&self, target: TableBgTarget, rgba: [f32; 4], column_n: i32) {
295 let col = crate::colors::Color::from_array(rgba).to_imgui_u32();
297 unsafe { sys::igTableSetBgColor(target as i32, col, column_n) }
298 }
299
300 #[doc(alias = "TableGetHoveredRow")]
302 pub fn table_get_hovered_row(&self) -> i32 {
303 unsafe { sys::igTableGetHoveredRow() }
304 }
305
306 #[doc(alias = "TableGetHeaderRowHeight")]
308 pub fn table_get_header_row_height(&self) -> f32 {
309 unsafe { sys::igTableGetHeaderRowHeight() }
310 }
311
312 #[doc(alias = "TableSetColumnSortDirection")]
314 pub fn table_set_column_sort_direction(
315 &self,
316 column_n: i32,
317 dir: SortDirection,
318 append_to_sort_specs: bool,
319 ) {
320 unsafe { sys::igTableSetColumnSortDirection(column_n, dir.into(), append_to_sort_specs) }
321 }
322
323 #[doc(alias = "TableGetSortSpecs")]
327 pub fn table_get_sort_specs(&self) -> Option<TableSortSpecs<'_>> {
328 unsafe {
329 let ptr = sys::igTableGetSortSpecs();
330 if ptr.is_null() {
331 None
332 } else {
333 Some(TableSortSpecs::from_raw(ptr))
334 }
335 }
336 }
337}
338
339bitflags::bitflags! {
340 #[repr(transparent)]
342 pub struct TableRowFlags: i32 {
343 const NONE = 0;
345 const HEADERS = sys::ImGuiTableRowFlags_Headers as i32;
347 }
348}
349
350#[repr(i32)]
352#[derive(Copy, Clone, Debug, PartialEq, Eq)]
353pub enum TableBgTarget {
354 None = sys::ImGuiTableBgTarget_None as i32,
356 RowBg0 = sys::ImGuiTableBgTarget_RowBg0 as i32,
358 RowBg1 = sys::ImGuiTableBgTarget_RowBg1 as i32,
360 CellBg = sys::ImGuiTableBgTarget_CellBg as i32,
362}
363
364#[repr(u8)]
366#[derive(Copy, Clone, Debug, PartialEq, Eq)]
367pub enum SortDirection {
368 None = sys::ImGuiSortDirection_None as u8,
369 Ascending = sys::ImGuiSortDirection_Ascending as u8,
370 Descending = sys::ImGuiSortDirection_Descending as u8,
371}
372
373impl From<SortDirection> for sys::ImGuiSortDirection {
374 #[inline]
375 fn from(value: SortDirection) -> sys::ImGuiSortDirection {
376 match value {
377 SortDirection::None => sys::ImGuiSortDirection_None,
378 SortDirection::Ascending => sys::ImGuiSortDirection_Ascending,
379 SortDirection::Descending => sys::ImGuiSortDirection_Descending,
380 }
381 }
382}
383
384#[derive(Copy, Clone, Debug)]
386pub struct TableColumnSortSpec {
387 pub column_user_id: u32,
388 pub column_index: i16,
389 pub sort_order: i16,
390 pub sort_direction: SortDirection,
391}
392
393pub struct TableSortSpecs<'a> {
395 raw: *mut sys::ImGuiTableSortSpecs,
396 _marker: std::marker::PhantomData<&'a Ui>,
397}
398
399impl<'a> TableSortSpecs<'a> {
400 pub(crate) unsafe fn from_raw(raw: *mut sys::ImGuiTableSortSpecs) -> Self {
403 Self {
404 raw,
405 _marker: std::marker::PhantomData,
406 }
407 }
408
409 pub fn is_dirty(&self) -> bool {
411 unsafe { (*self.raw).SpecsDirty }
412 }
413
414 pub fn clear_dirty(&mut self) {
416 unsafe { (*self.raw).SpecsDirty = false }
417 }
418
419 pub fn len(&self) -> usize {
421 unsafe { (*self.raw).SpecsCount as usize }
422 }
423
424 pub fn is_empty(&self) -> bool {
425 self.len() == 0
426 }
427
428 pub fn iter(&self) -> TableSortSpecsIter<'_> {
430 TableSortSpecsIter {
431 specs: self,
432 index: 0,
433 }
434 }
435}
436
437pub struct TableSortSpecsIter<'a> {
439 specs: &'a TableSortSpecs<'a>,
440 index: usize,
441}
442
443impl<'a> Iterator for TableSortSpecsIter<'a> {
444 type Item = TableColumnSortSpec;
445 fn next(&mut self) -> Option<Self::Item> {
446 if self.index >= self.specs.len() {
447 return None;
448 }
449 unsafe {
450 let ptr = (*self.specs.raw).Specs;
451 if ptr.is_null() {
452 return None;
453 }
454 let spec = &*ptr.add(self.index);
455 self.index += 1;
456 let d = spec.SortDirection as u8;
457 let dir = if d == sys::ImGuiSortDirection_None as u8 {
458 SortDirection::None
459 } else if d == sys::ImGuiSortDirection_Ascending as u8 {
460 SortDirection::Ascending
461 } else if d == sys::ImGuiSortDirection_Descending as u8 {
462 SortDirection::Descending
463 } else {
464 SortDirection::None
465 };
466 Some(TableColumnSortSpec {
467 column_user_id: spec.ColumnUserID,
468 column_index: spec.ColumnIndex,
469 sort_order: spec.SortOrder,
470 sort_direction: dir,
471 })
472 }
473 }
474}
475
476#[must_use]
478pub struct TableToken<'ui> {
479 ui: &'ui Ui,
480}
481
482impl<'ui> TableToken<'ui> {
483 fn new(ui: &'ui Ui) -> Self {
485 TableToken { ui }
486 }
487
488 pub fn end(self) {
490 }
492}
493
494impl<'ui> Drop for TableToken<'ui> {
495 fn drop(&mut self) {
496 unsafe {
497 sys::igEndTable();
498 }
499 }
500}
501
502#[derive(Copy, Clone, Debug, PartialEq)]
508pub struct TableHeaderData {
509 pub index: i16,
510 pub text_color: ImColor32,
511 pub bg_color0: ImColor32,
512 pub bg_color1: ImColor32,
513}
514
515impl TableHeaderData {
516 pub fn new(
517 index: i16,
518 text_color: ImColor32,
519 bg_color0: ImColor32,
520 bg_color1: ImColor32,
521 ) -> Self {
522 Self {
523 index,
524 text_color,
525 bg_color0,
526 bg_color1,
527 }
528 }
529}
530impl Ui {
531 #[doc(alias = "TableGetHeaderAngledMaxLabelWidth")]
533 pub fn table_get_header_angled_max_label_width(&self) -> f32 {
534 unsafe { sys::igTableGetHeaderAngledMaxLabelWidth() }
535 }
536
537 #[doc(alias = "TableAngledHeadersRow")]
539 pub fn table_angled_headers_row(&self) {
540 unsafe { sys::igTableAngledHeadersRow() }
541 }
542
543 pub fn table_angled_headers_row_ex_with_data(
552 &self,
553 row_id: u32,
554 angle: f32,
555 max_label_width: f32,
556 headers: &[TableHeaderData],
557 ) {
558 if headers.is_empty() {
559 unsafe { sys::igTableAngledHeadersRow() }
560 return;
561 }
562 let mut data: Vec<sys::ImGuiTableHeaderData> = Vec::with_capacity(headers.len());
563 for h in headers {
564 data.push(sys::ImGuiTableHeaderData {
565 Index: h.index as sys::ImGuiTableColumnIdx,
566 TextColor: u32::from(h.text_color),
567 BgColor0: u32::from(h.bg_color0),
568 BgColor1: u32::from(h.bg_color1),
569 });
570 }
571 unsafe {
572 sys::igTableAngledHeadersRowEx(
573 row_id,
574 angle,
575 max_label_width,
576 data.as_ptr(),
577 data.len() as i32,
578 );
579 }
580 }
581
582 #[doc(alias = "TablePushBackgroundChannel")]
584 pub fn table_push_background_channel(&self) {
585 unsafe { sys::igTablePushBackgroundChannel() }
586 }
587
588 #[doc(alias = "TablePopBackgroundChannel")]
590 pub fn table_pop_background_channel(&self) {
591 unsafe { sys::igTablePopBackgroundChannel() }
592 }
593
594 #[doc(alias = "TablePushColumnChannel")]
596 pub fn table_push_column_channel(&self, column_n: i32) {
597 unsafe { sys::igTablePushColumnChannel(column_n) }
598 }
599
600 #[doc(alias = "TablePopColumnChannel")]
602 pub fn table_pop_column_channel(&self) {
603 unsafe { sys::igTablePopColumnChannel() }
604 }
605
606 pub fn with_table_background_channel<R>(&self, f: impl FnOnce() -> R) -> R {
608 self.table_push_background_channel();
609 let result = f();
610 self.table_pop_background_channel();
611 result
612 }
613
614 pub fn with_table_column_channel<R>(&self, column_n: i32, f: impl FnOnce() -> R) -> R {
616 self.table_push_column_channel(column_n);
617 let result = f();
618 self.table_pop_column_channel();
619 result
620 }
621
622 #[doc(alias = "TableOpenContextMenu")]
624 pub fn table_open_context_menu(&self, column_n: Option<i32>) {
625 unsafe { sys::igTableOpenContextMenu(column_n.unwrap_or(-1)) }
626 }
627}
628
629#[derive(Debug)]
637pub struct TableBuilder<'ui> {
638 ui: &'ui Ui,
639 id: String,
640 flags: TableFlags,
641 outer_size: [f32; 2],
642 inner_width: f32,
643 columns: Vec<TableColumnSetup<String>>, use_headers: bool,
645 freeze: Option<(i32, i32)>,
646}
647
648impl<'ui> TableBuilder<'ui> {
649 pub fn new(ui: &'ui Ui, str_id: impl AsRef<str>) -> Self {
651 Self {
652 ui,
653 id: str_id.as_ref().to_string(),
654 flags: TableFlags::NONE,
655 outer_size: [0.0, 0.0],
656 inner_width: 0.0,
657 columns: Vec::new(),
658 use_headers: false,
659 freeze: None,
660 }
661 }
662
663 pub fn flags(mut self, flags: TableFlags) -> Self {
665 self.flags = flags;
666 self
667 }
668
669 pub fn outer_size(mut self, size: [f32; 2]) -> Self {
671 self.outer_size = size;
672 self
673 }
674
675 pub fn inner_width(mut self, width: f32) -> Self {
677 self.inner_width = width;
678 self
679 }
680
681 pub fn freeze(mut self, frozen_cols: i32, frozen_rows: i32) -> Self {
683 self.freeze = Some((frozen_cols, frozen_rows));
684 self
685 }
686
687 pub fn column(self, name: impl AsRef<str>) -> ColumnBuilder<'ui> {
690 ColumnBuilder::new(self, name)
691 }
692
693 pub fn columns<Name: AsRef<str>>(
695 mut self,
696 cols: impl IntoIterator<Item = TableColumnSetup<Name>>,
697 ) -> Self {
698 self.columns.clear();
699 for c in cols {
700 self.columns.push(TableColumnSetup {
701 name: c.name.as_ref().to_string(),
702 flags: c.flags,
703 init_width_or_weight: c.init_width_or_weight,
704 user_id: c.user_id,
705 });
706 }
707 self
708 }
709
710 pub fn add_column<Name: AsRef<str>>(mut self, col: TableColumnSetup<Name>) -> Self {
712 self.columns.push(TableColumnSetup {
713 name: col.name.as_ref().to_string(),
714 flags: col.flags,
715 init_width_or_weight: col.init_width_or_weight,
716 user_id: col.user_id,
717 });
718 self
719 }
720
721 pub fn headers(mut self, enabled: bool) -> Self {
723 self.use_headers = enabled;
724 self
725 }
726
727 pub fn build(self, f: impl FnOnce(&Ui)) {
729 let Some(token) = self.ui.begin_table_with_sizing(
730 &self.id,
731 self.columns.len(),
732 self.flags,
733 self.outer_size,
734 self.inner_width,
735 ) else {
736 return;
737 };
738
739 if let Some((fc, fr)) = self.freeze {
740 self.ui.table_setup_scroll_freeze(fc, fr);
741 }
742
743 if !self.columns.is_empty() {
744 for col in &self.columns {
745 self.ui.table_setup_column(
746 &col.name,
747 col.flags,
748 col.init_width_or_weight,
749 col.user_id,
750 );
751 }
752 if self.use_headers {
753 self.ui.table_headers_row();
754 }
755 }
756
757 f(self.ui);
758
759 token.end();
761 }
762}
763
764#[derive(Debug)]
766pub struct ColumnBuilder<'ui> {
767 parent: TableBuilder<'ui>,
768 name: String,
769 flags: TableColumnFlags,
770 init_width_or_weight: f32,
771 user_id: u32,
772}
773
774impl<'ui> ColumnBuilder<'ui> {
775 fn new(parent: TableBuilder<'ui>, name: impl AsRef<str>) -> Self {
776 Self {
777 parent,
778 name: name.as_ref().to_string(),
779 flags: TableColumnFlags::NONE,
780 init_width_or_weight: 0.0,
781 user_id: 0,
782 }
783 }
784
785 pub fn flags(mut self, flags: TableColumnFlags) -> Self {
787 self.flags = flags;
788 self
789 }
790
791 pub fn width(mut self, width: f32) -> Self {
793 self.init_width_or_weight = width;
794 self
795 }
796
797 pub fn weight(self, weight: f32) -> Self {
799 self.width(weight)
800 }
801
802 pub fn angled_header(mut self, enabled: bool) -> Self {
804 if enabled {
805 self.flags.insert(TableColumnFlags::ANGLED_HEADER);
806 } else {
807 self.flags.remove(TableColumnFlags::ANGLED_HEADER);
808 }
809 self
810 }
811
812 pub fn user_id(mut self, id: u32) -> Self {
814 self.user_id = id;
815 self
816 }
817
818 pub fn done(mut self) -> TableBuilder<'ui> {
820 self.parent.columns.push(TableColumnSetup {
821 name: self.name,
822 flags: self.flags,
823 init_width_or_weight: self.init_width_or_weight,
824 user_id: self.user_id,
825 });
826 self.parent
827 }
828}