dioxus_tabular/context/
column_order.rs

1/// Manages the order and visibility of columns in a table.
2#[derive(Clone, PartialEq, Debug)]
3pub struct ColumnOrder {
4    /// The order of visible columns (indices into the column tuple)
5    order: Vec<usize>,
6    /// Total number of columns available
7    total_columns: usize,
8}
9
10impl ColumnOrder {
11    /// Creates a new ColumnOrder with default ordering (all columns visible in natural order)
12    pub fn new(total_columns: usize) -> Self {
13        Self {
14            order: (0..total_columns).collect(),
15            total_columns,
16        }
17    }
18
19    /// Returns the current column order as a slice
20    pub fn get_order(&self) -> &[usize] {
21        &self.order
22    }
23
24    /// Returns the total number of columns
25    pub fn total_columns(&self) -> usize {
26        self.total_columns
27    }
28
29    /// Swaps two columns in the display order.
30    /// If either column is hidden or out of bounds, this is a no-op (saturating behavior).
31    pub fn swap(&mut self, col_a: usize, col_b: usize) {
32        // Saturate to valid column indices
33        let col_a = col_a.min(self.total_columns.saturating_sub(1));
34        let col_b = col_b.min(self.total_columns.saturating_sub(1));
35
36        // Find positions in the order vector
37        let pos_a = self.order.iter().position(|&c| c == col_a);
38        let pos_b = self.order.iter().position(|&c| c == col_b);
39
40        // Only swap if both are visible
41        if let (Some(pos_a), Some(pos_b)) = (pos_a, pos_b) {
42            self.order.swap(pos_a, pos_b);
43        }
44    }
45
46    /// Hides a column by removing it from the display order.
47    /// If the column is already hidden or out of bounds, this is a no-op.
48    pub fn hide_column(&mut self, col: usize) {
49        // Saturate to valid column index
50        let col = col.min(self.total_columns.saturating_sub(1));
51
52        // Remove from order if present
53        self.order.retain(|&c| c != col);
54    }
55
56    /// Shows a column by inserting it into the display order.
57    /// If at_index is None, appends to the end.
58    /// If at_index is Some(idx), inserts at that position (saturated to valid range).
59    /// If the column is already visible or out of bounds, this is a no-op.
60    pub fn show_column(&mut self, col: usize, at_index: Option<usize>) {
61        // Saturate to valid column index
62        let col = col.min(self.total_columns.saturating_sub(1));
63
64        // If already visible, do nothing
65        if self.order.contains(&col) {
66            return;
67        }
68
69        // Insert at specified position or append
70        match at_index {
71            None => self.order.push(col),
72            Some(idx) => {
73                let insert_pos = idx.min(self.order.len());
74                self.order.insert(insert_pos, col);
75            }
76        }
77    }
78
79    /// Moves a column to a specific position in the display order (0-indexed).
80    /// The position is saturated to the valid range.
81    /// If the column is hidden or out of bounds, this is a no-op.
82    pub fn move_to(&mut self, col: usize, new_index: usize) {
83        // Saturate to valid column index
84        let col = col.min(self.total_columns.saturating_sub(1));
85
86        // Find current position
87        if let Some(current_pos) = self.order.iter().position(|&c| c == col) {
88            // Remove from current position
89            self.order.remove(current_pos);
90
91            // Insert at new position (saturated)
92            let insert_pos = new_index.min(self.order.len());
93            self.order.insert(insert_pos, col);
94        }
95    }
96
97    /// Moves a column one position forward (towards index 0) in the display order.
98    /// If the column is already first or hidden, this is a no-op.
99    pub fn move_forward(&mut self, col: usize) {
100        // Saturate to valid column index
101        let col = col.min(self.total_columns.saturating_sub(1));
102
103        if let Some(pos) = self.order.iter().position(|&c| c == col) && pos > 0 {
104            self.order.swap(pos, pos - 1);
105        }
106    }
107
108    /// Moves a column one position backward (towards the end) in the display order.
109    /// If the column is already last or hidden, this is a no-op.
110    pub fn move_backward(&mut self, col: usize) {
111        // Saturate to valid column index
112        let col = col.min(self.total_columns.saturating_sub(1));
113
114        if let Some(pos) = self.order.iter().position(|&c| c == col) && pos < self.order.len() - 1 {
115            self.order.swap(pos, pos + 1);
116        }
117    }
118
119    /// Checks if a column is currently visible
120    pub fn is_visible(&self, col: usize) -> bool {
121        // Saturate to valid column index
122        let col = col.min(self.total_columns.saturating_sub(1));
123        self.order.contains(&col)
124    }
125
126    /// Returns the display position of a column (0-indexed), or None if hidden
127    pub fn position(&self, col: usize) -> Option<usize> {
128        // Saturate to valid column index
129        let col = col.min(self.total_columns.saturating_sub(1));
130        self.order.iter().position(|&c| c == col)
131    }
132
133    /// Resets the column order to the default state (all columns visible in natural order)
134    pub fn reset(&mut self) {
135        self.order = (0..self.total_columns).collect();
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    #[test]
144    fn test_new() {
145        let order = ColumnOrder::new(3);
146        assert_eq!(order.get_order(), &[0, 1, 2]);
147        assert_eq!(order.total_columns(), 3);
148    }
149
150    #[test]
151    fn test_swap() {
152        let mut order = ColumnOrder::new(3);
153        order.swap(0, 2);
154        assert_eq!(order.get_order(), &[2, 1, 0]);
155    }
156
157    #[test]
158    fn test_swap_saturating() {
159        let mut order = ColumnOrder::new(3);
160        order.swap(0, 100); // Should saturate to 2
161        assert_eq!(order.get_order(), &[2, 1, 0]);
162    }
163
164    #[test]
165    fn test_hide_show() {
166        let mut order = ColumnOrder::new(3);
167
168        order.hide_column(1);
169        assert_eq!(order.get_order(), &[0, 2]);
170        assert!(!order.is_visible(1));
171
172        order.show_column(1, None);
173        assert_eq!(order.get_order(), &[0, 2, 1]);
174        assert!(order.is_visible(1));
175    }
176
177    #[test]
178    fn test_show_at_index() {
179        let mut order = ColumnOrder::new(3);
180        order.hide_column(1);
181        order.show_column(1, Some(0));
182        assert_eq!(order.get_order(), &[1, 0, 2]);
183    }
184
185    #[test]
186    fn test_move_to() {
187        let mut order = ColumnOrder::new(3);
188        order.move_to(0, 2);
189        assert_eq!(order.get_order(), &[1, 2, 0]);
190    }
191
192    #[test]
193    fn test_move_forward_backward() {
194        let mut order = ColumnOrder::new(3);
195
196        order.move_backward(0);
197        assert_eq!(order.get_order(), &[1, 0, 2]);
198
199        order.move_forward(0);
200        assert_eq!(order.get_order(), &[0, 1, 2]);
201    }
202
203    #[test]
204    fn test_move_forward_at_start() {
205        let mut order = ColumnOrder::new(3);
206        order.move_forward(0);
207        assert_eq!(order.get_order(), &[0, 1, 2]); // No change
208    }
209
210    #[test]
211    fn test_move_backward_at_end() {
212        let mut order = ColumnOrder::new(3);
213        order.move_backward(2);
214        assert_eq!(order.get_order(), &[0, 1, 2]); // No change
215    }
216
217    #[test]
218    fn test_position() {
219        let mut order = ColumnOrder::new(3);
220        assert_eq!(order.position(1), Some(1));
221
222        order.hide_column(1);
223        assert_eq!(order.position(1), None);
224    }
225
226    #[test]
227    fn test_reset() {
228        let mut order = ColumnOrder::new(3);
229
230        // Make some changes
231        order.hide_column(1);
232        order.swap(0, 2);
233        assert_eq!(order.get_order(), &[2, 0]);
234
235        // Reset should restore default order
236        order.reset();
237        assert_eq!(order.get_order(), &[0, 1, 2]);
238        assert!(order.is_visible(1));
239    }
240}