ankurah_virtual_scroll/
windowing.rs1pub fn screen_items(viewport_height: u32, minimum_row_height: u32) -> usize {
12 let items = (viewport_height as f64 / minimum_row_height as f64).ceil() as usize;
13 items.max(1) }
15
16pub fn live_window_size(screen_items: usize, threshold_screens: f64) -> usize {
18 ((2.0 * threshold_screens + 1.0) * screen_items as f64).ceil() as usize
19}
20
21pub fn full_window_size(screen_items: usize, threshold_screens: f64) -> usize {
23 ((4.0 * threshold_screens + 1.0) * screen_items as f64).ceil() as usize
24}
25
26pub fn trigger_threshold_px(viewport_height: u32, threshold_screens: f64) -> u32 {
28 (threshold_screens * viewport_height as f64).ceil() as u32
29}
30
31pub fn continuation_offset(screen_items: usize, threshold_screens: f64) -> usize {
33 (threshold_screens * screen_items as f64).ceil() as usize
34}
35
36pub fn min_buffer(screen_items: usize, threshold_screens: f64) -> usize {
38 (threshold_screens * screen_items as f64).ceil() as usize
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum Direction {
48 Backward,
50 Forward,
52}
53
54#[derive(Debug, Clone, PartialEq)]
56pub enum TriggerCheck {
57 None,
59 Trigger(Direction),
61}
62
63pub fn check_trigger(
65 trigger_threshold_px: u32,
66 top_gap_px: u32,
67 bottom_gap_px: u32,
68 scrolling_up: bool,
69 at_earliest: bool,
70 at_latest: bool,
71) -> TriggerCheck {
72 if scrolling_up && top_gap_px < trigger_threshold_px && !at_earliest {
74 return TriggerCheck::Trigger(Direction::Backward);
75 }
76
77 if !scrolling_up && bottom_gap_px < trigger_threshold_px && !at_latest {
79 return TriggerCheck::Trigger(Direction::Forward);
80 }
81
82 TriggerCheck::None
83}
84
85pub fn continuation_index(
89 continuation_offset: usize,
90 current_len: usize,
91 direction: Direction,
92) -> usize {
93 match direction {
94 Direction::Backward => {
95 current_len.saturating_sub(continuation_offset)
97 }
98 Direction::Forward => {
99 continuation_offset.saturating_sub(1).min(current_len.saturating_sub(1))
101 }
102 }
103}
104
105#[derive(Debug, Clone, PartialEq)]
113pub struct WindowingParams {
114 pub screen_items: usize,
115 pub live_window_size: usize,
116 pub full_window_size: usize,
117 pub min_buffer: usize,
118 pub trigger_threshold_px: u32,
119 pub continuation_offset: usize,
120}
121
122impl WindowingParams {
123 pub fn compute(
125 viewport_height: u32,
126 minimum_row_height: u32,
127 threshold_screens: f64,
128 ) -> Self {
129 let screen_items = screen_items(viewport_height, minimum_row_height);
130 Self {
131 screen_items,
132 live_window_size: live_window_size(screen_items, threshold_screens),
133 full_window_size: full_window_size(screen_items, threshold_screens),
134 min_buffer: min_buffer(screen_items, threshold_screens),
135 trigger_threshold_px: trigger_threshold_px(viewport_height, threshold_screens),
136 continuation_offset: continuation_offset(screen_items, threshold_screens),
137 }
138 }
139}
140
141pub fn simulate_pagination(
148 full_window_size: usize,
149 _current_start_id: i64,
150 _current_end_id: i64,
151 continuation_id: i64,
152 direction: Direction,
153 total_items_in_db: i64,
154) -> (i64, i64) {
155 let window_size = full_window_size as i64;
156
157 match direction {
158 Direction::Backward => {
159 let new_end = continuation_id;
161 let new_start = (continuation_id - window_size + 1).max(0);
162 (new_start, new_end)
163 }
164 Direction::Forward => {
165 let new_start = continuation_id;
167 let new_end = (continuation_id + window_size - 1).min(total_items_in_db - 1);
168 (new_start, new_end)
169 }
170 }
171}
172
173pub fn find_intersection_range(
177 old_start: i64,
178 old_end: i64,
179 new_start: i64,
180 new_end: i64,
181) -> Option<(i64, i64)> {
182 let inter_start = old_start.max(new_start);
183 let inter_end = old_end.min(new_end);
184
185 if inter_start <= inter_end {
186 Some((inter_start, inter_end))
187 } else {
188 None
189 }
190}
191
192pub fn select_anchor_id(
196 intersection_start: i64,
197 intersection_end: i64,
198 direction: Direction,
199) -> i64 {
200 match direction {
201 Direction::Backward => intersection_start,
203 Direction::Forward => intersection_end,
205 }
206}
207
208pub fn visible_items_preserved(
210 visible_start_id: i64,
211 visible_end_id: i64,
212 new_start_id: i64,
213 new_end_id: i64,
214) -> bool {
215 visible_start_id >= new_start_id && visible_end_id <= new_end_id
216}
217
218pub fn calculate_buffers(
222 visible_start_id: i64,
223 visible_end_id: i64,
224 new_start_id: i64,
225 new_end_id: i64,
226) -> (i64, i64) {
227 let above = visible_start_id - new_start_id;
228 let below = new_end_id - visible_end_id;
229 (above.max(0), below.max(0))
230}