Skip to main content

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}