Skip to main content

fret_ui_headless/table/
pagination.rs

1use super::{RowIndex, RowModel};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub struct PaginationState {
5    pub page_index: usize,
6    pub page_size: usize,
7}
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub struct PaginationBounds {
11    pub page_index: usize,
12    pub page_count: usize,
13    pub can_prev: bool,
14    pub can_next: bool,
15    pub page_start: usize,
16    pub page_end: usize,
17}
18
19impl Default for PaginationState {
20    fn default() -> Self {
21        Self {
22            page_index: 0,
23            page_size: 10,
24        }
25    }
26}
27
28pub fn pagination_bounds(total_rows: usize, pagination: PaginationState) -> PaginationBounds {
29    if pagination.page_size == 0 || total_rows == 0 {
30        return PaginationBounds {
31            page_index: 0,
32            page_count: 0,
33            can_prev: false,
34            can_next: false,
35            page_start: 0,
36            page_end: 0,
37        };
38    }
39
40    let page_count = total_rows.div_ceil(pagination.page_size);
41    let page_index = pagination.page_index.min(page_count.saturating_sub(1));
42    let page_start = page_index.saturating_mul(pagination.page_size);
43    let page_end = (page_start.saturating_add(pagination.page_size)).min(total_rows);
44
45    PaginationBounds {
46        page_index,
47        page_count,
48        can_prev: page_index > 0,
49        can_next: page_index + 1 < page_count,
50        page_start,
51        page_end,
52    }
53}
54
55pub fn paginate_row_model<'a, TData>(
56    row_model: &RowModel<'a, TData>,
57    pagination: PaginationState,
58) -> RowModel<'a, TData> {
59    if row_model.root_rows().is_empty() {
60        return row_model.clone();
61    }
62    if pagination.page_size == 0 {
63        return RowModel {
64            root_rows: Vec::new(),
65            flat_rows: Vec::new(),
66            rows_by_key: row_model.rows_by_key().clone(),
67            rows_by_id: row_model.rows_by_id().clone(),
68            arena: row_model.arena().to_vec(),
69        };
70    }
71
72    let page_start = pagination.page_index.saturating_mul(pagination.page_size);
73    let page_end = page_start.saturating_add(pagination.page_size);
74
75    let mut out = row_model.clone();
76    let roots = out.root_rows.clone();
77    let start = page_start.min(roots.len());
78    let end = page_end.min(roots.len());
79    out.root_rows = roots[start..end].to_vec();
80
81    out.flat_rows.clear();
82    fn push_flat<TData>(row_model: &mut RowModel<'_, TData>, row: RowIndex) {
83        row_model.flat_rows.push(row);
84        let Some(r) = row_model.row(row) else {
85            return;
86        };
87        let children = r.sub_rows.clone();
88        for child in children {
89            push_flat(row_model, child);
90        }
91    }
92
93    let roots = out.root_rows.clone();
94    for root in roots {
95        push_flat(&mut out, root);
96    }
97
98    out
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104    use crate::table::{RowKey, Table};
105
106    #[derive(Debug, Clone)]
107    struct Item {
108        #[allow(dead_code)]
109        value: i32,
110    }
111
112    #[test]
113    fn paginate_row_model_slices_root_rows_and_rebuilds_flat_rows() {
114        let data = (0..5).map(|i| Item { value: i }).collect::<Vec<_>>();
115        let table = Table::builder(&data).build();
116        let core = table.core_row_model();
117
118        let paged = paginate_row_model(
119            core,
120            PaginationState {
121                page_index: 1,
122                page_size: 2,
123            },
124        );
125
126        let ids = paged
127            .root_rows()
128            .iter()
129            .filter_map(|&i| paged.row(i).map(|r| r.key.0))
130            .collect::<Vec<_>>();
131        assert_eq!(ids, vec![2, 3]);
132        assert_eq!(paged.flat_rows().len(), 2);
133        assert!(
134            paged.row_by_key(RowKey::from_index(0)).is_some(),
135            "rows_by_key remains full"
136        );
137        assert!(
138            paged.row_by_key(RowKey::from_index(4)).is_some(),
139            "rows_by_key remains full"
140        );
141    }
142
143    #[test]
144    fn paginate_row_model_clamps_page_end_to_row_count() {
145        let data = (0..5).map(|i| Item { value: i }).collect::<Vec<_>>();
146        let table = Table::builder(&data).build();
147        let core = table.core_row_model();
148
149        let paged = paginate_row_model(
150            core,
151            PaginationState {
152                page_index: 0,
153                page_size: 10,
154            },
155        );
156
157        let ids = paged
158            .root_rows()
159            .iter()
160            .filter_map(|&i| paged.row(i).map(|r| r.key.0))
161            .collect::<Vec<_>>();
162        assert_eq!(ids, vec![0, 1, 2, 3, 4]);
163    }
164
165    #[test]
166    fn pagination_bounds_clamps_and_reports_can_next_prev() {
167        let b = pagination_bounds(
168            5,
169            PaginationState {
170                page_index: 0,
171                page_size: 2,
172            },
173        );
174        assert_eq!(
175            b,
176            PaginationBounds {
177                page_index: 0,
178                page_count: 3,
179                can_prev: false,
180                can_next: true,
181                page_start: 0,
182                page_end: 2,
183            }
184        );
185
186        let b = pagination_bounds(
187            5,
188            PaginationState {
189                page_index: 10,
190                page_size: 2,
191            },
192        );
193        assert_eq!(b.page_index, 2);
194        assert_eq!(b.page_count, 3);
195        assert!(!b.can_next);
196        assert!(b.can_prev);
197        assert_eq!((b.page_start, b.page_end), (4, 5));
198
199        let b = pagination_bounds(
200            0,
201            PaginationState {
202                page_index: 0,
203                page_size: 10,
204            },
205        );
206        assert_eq!(b.page_count, 0);
207        assert_eq!(b.page_index, 0);
208        assert!(!b.can_prev);
209        assert!(!b.can_next);
210    }
211}