oxiui_table/height.rs
1//! Prefix-sum cache for variable-height row scroll lookup.
2//!
3//! When a [`RowSource`] overrides [`crate::RowSource::row_height`]
4//! to return per-row heights, [`CumulativeHeights`] pre-computes a prefix-sum array
5//! so that scroll-offset-to-row-index translation is O(log n) instead of O(n).
6
7use crate::RowSource;
8
9/// Prefix-sum cache for variable-height row scroll lookups.
10///
11/// Build once per frame (or whenever the row set changes) with
12/// [`CumulativeHeights::build`], then call [`row_at_offset`](Self::row_at_offset)
13/// and [`visible_range`](Self::visible_range) as needed during rendering.
14pub struct CumulativeHeights {
15 /// `cumulative[i]` = sum of `row_height(0..i)`.
16 /// Length is `row_count + 1`; `cumulative[0]` is always `0.0`.
17 cumulative: Vec<f32>,
18}
19
20impl CumulativeHeights {
21 /// Build the prefix-sum array from a [`RowSource`].
22 ///
23 /// Calls [`RowSource::row_height`] once per row and accumulates the results.
24 pub fn build<S: RowSource + ?Sized>(source: &S) -> Self {
25 let n = source.row_count();
26 let mut cumulative = vec![0.0f32; n + 1];
27 for i in 0..n {
28 cumulative[i + 1] = cumulative[i] + source.row_height(i);
29 }
30 Self { cumulative }
31 }
32
33 /// Total height of all rows in logical pixels.
34 pub fn total_height(&self) -> f32 {
35 self.cumulative.last().copied().unwrap_or(0.0)
36 }
37
38 /// Return the index of the first row whose top edge is at or before
39 /// `scroll_offset`.
40 ///
41 /// Uses a binary search for O(log n) lookup.
42 pub fn row_at_offset(&self, scroll_offset: f32) -> usize {
43 match self.cumulative.partition_point(|&c| c <= scroll_offset) {
44 0 => 0,
45 n => (n - 1).min(self.cumulative.len().saturating_sub(2)),
46 }
47 }
48
49 /// Return the range of row indices that are (at least partially) visible
50 /// in a viewport starting at `scroll_offset` with height `viewport_height`.
51 pub fn visible_range(
52 &self,
53 scroll_offset: f32,
54 viewport_height: f32,
55 ) -> std::ops::Range<usize> {
56 let start = self.row_at_offset(scroll_offset);
57 let end_offset = scroll_offset + viewport_height;
58 let end = self
59 .cumulative
60 .partition_point(|&c| c < end_offset)
61 .min(self.cumulative.len().saturating_sub(1));
62 start..end
63 }
64}