Skip to main content

dear_imgui_rs/widget/table/
sort.rs

1use crate::sys;
2use crate::ui::Ui;
3use crate::widget::table::{TableColumnIndex, TableColumnUserData};
4#[cfg(feature = "serde")]
5use serde::{Deserialize, Serialize};
6
7/// Sorting direction for table columns.
8#[repr(u8)]
9#[derive(Copy, Clone, Debug, PartialEq, Eq)]
10#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
11pub enum SortDirection {
12    None = sys::ImGuiSortDirection_None as u8,
13    Ascending = sys::ImGuiSortDirection_Ascending as u8,
14    Descending = sys::ImGuiSortDirection_Descending as u8,
15}
16
17impl From<SortDirection> for sys::ImGuiSortDirection {
18    #[inline]
19    fn from(value: SortDirection) -> sys::ImGuiSortDirection {
20        match value {
21            SortDirection::None => sys::ImGuiSortDirection_None,
22            SortDirection::Ascending => sys::ImGuiSortDirection_Ascending,
23            SortDirection::Descending => sys::ImGuiSortDirection_Descending,
24        }
25    }
26}
27
28/// One column sort spec.
29#[derive(Copy, Clone, Debug, PartialEq, Eq)]
30pub struct TableColumnSortSpec {
31    pub column_user_data: TableColumnUserData,
32    pub column_index: TableColumnIndex,
33    pub sort_order: i16,
34    pub sort_direction: SortDirection,
35}
36
37/// Owned snapshot of the current table sort specifications.
38///
39/// The column data remains valid after the table ends. Clearing Dear ImGui's dirty flag is still
40/// a table-scoped operation, so [`Self::clear_dirty`] validates that the source table is current in
41/// the same frame before mutating native state.
42#[derive(Debug)]
43pub struct TableSortSpecs {
44    specs: Box<[TableColumnSortSpec]>,
45    dirty: bool,
46    source_context: *mut sys::ImGuiContext,
47    source_scope: crate::scope::TableScope,
48}
49
50impl TableSortSpecs {
51    /// # Safety
52    /// `table` and `raw` must belong to `ui`'s current table in the current frame.
53    pub(crate) unsafe fn from_raw(
54        ui: &Ui,
55        table: *mut sys::ImGuiTable,
56        raw: *mut sys::ImGuiTableSortSpecs,
57    ) -> Self {
58        debug_assert_eq!(unsafe { sys::igGetCurrentTable() }, table);
59        let (dirty, specs) = unsafe { copy_table_sort_specs(raw) };
60        let source_scope = ui
61            .current_native_scope()
62            .table()
63            .expect("TableSortSpecs::from_raw() requires a current table");
64        Self {
65            specs,
66            dirty,
67            source_context: ui.context_raw(),
68            source_scope,
69        }
70    }
71
72    /// Whether the specs are marked dirty by dear imgui (you should resort your data).
73    pub fn is_dirty(&self) -> bool {
74        self.dirty
75    }
76
77    /// Clear the native dirty flag after applying this snapshot.
78    ///
79    /// # Panics
80    ///
81    /// Panics if `ui` belongs to another Context or if the exact frame, window `Begin`, and table
82    /// instance that produced the snapshot is no longer current. The snapshot data itself remains
83    /// readable after any of those conditions; only native acknowledgement is table-scoped.
84    pub fn clear_dirty(&mut self, ui: &Ui) {
85        if !self.dirty {
86            return;
87        }
88        assert!(
89            std::ptr::eq(ui.context_raw(), self.source_context),
90            "TableSortSpecs::clear_dirty() requires the Ui that produced this snapshot"
91        );
92        ui.run_with_bound_context(|| unsafe {
93            assert_eq!(
94                ui.current_native_scope().table(),
95                Some(self.source_scope),
96                "TableSortSpecs::clear_dirty() source table is no longer current"
97            );
98            let raw = sys::igTableGetSortSpecs();
99            assert!(
100                !raw.is_null(),
101                "TableSortSpecs::clear_dirty() current table has no sort specifications"
102            );
103            (*raw).SpecsDirty = false;
104        });
105        self.dirty = false;
106    }
107
108    /// Number of column specs.
109    pub fn len(&self) -> usize {
110        self.specs.len()
111    }
112
113    pub fn is_empty(&self) -> bool {
114        self.len() == 0
115    }
116
117    /// Iterate over column sort specs.
118    pub fn iter(&self) -> std::slice::Iter<'_, TableColumnSortSpec> {
119        self.specs.iter()
120    }
121}
122
123pub(super) unsafe fn copy_table_sort_specs(
124    raw: *mut sys::ImGuiTableSortSpecs,
125) -> (bool, Box<[TableColumnSortSpec]>) {
126    assert!(!raw.is_null(), "table sort specs pointer must not be null");
127    let count = usize::try_from(unsafe { (*raw).SpecsCount })
128        .expect("Dear ImGui returned a negative table sort specification count");
129    let source = unsafe { (*raw).Specs };
130    assert!(
131        count == 0 || !source.is_null(),
132        "Dear ImGui returned null table sort specification data for a non-empty snapshot"
133    );
134    let mut specs = Vec::with_capacity(count);
135    for index in 0..count {
136        let spec = unsafe { &*source.add(index) };
137        let direction = match spec.SortDirection as u8 {
138            value if value == sys::ImGuiSortDirection_Ascending as u8 => SortDirection::Ascending,
139            value if value == sys::ImGuiSortDirection_Descending as u8 => SortDirection::Descending,
140            _ => SortDirection::None,
141        };
142        specs.push(TableColumnSortSpec {
143            column_user_data: TableColumnUserData::new(spec.ColumnUserID),
144            column_index: TableColumnIndex::from_imgui_column_idx(
145                spec.ColumnIndex,
146                "TableSortSpecs::from_raw()",
147            ),
148            sort_order: spec.SortOrder,
149            sort_direction: direction,
150        });
151    }
152    (unsafe { (*raw).SpecsDirty }, specs.into_boxed_slice())
153}