1use std::collections::HashMap;
12use std::ops::Range;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub struct ItemIndex(pub usize);
17
18impl ItemIndex {
19 pub fn as_usize(self) -> usize {
20 self.0
21 }
22}
23
24impl From<usize> for ItemIndex {
25 fn from(v: usize) -> Self {
26 Self(v)
27 }
28}
29
30#[derive(Debug, Clone)]
32pub struct VirtualListConfig {
33 pub estimated_item_height: f32,
35 pub overscan_count: usize,
37 pub variable_heights: bool,
39 pub initial_scroll: f32,
41 pub load_more_threshold: f32,
43}
44
45impl Default for VirtualListConfig {
46 fn default() -> Self {
47 Self {
48 estimated_item_height: 50.0,
49 overscan_count: 3,
50 variable_heights: false,
51 initial_scroll: 0.0,
52 load_more_threshold: 100.0,
53 }
54 }
55}
56
57#[derive(Debug, Clone, PartialEq)]
59pub struct VisibleRange {
60 pub start: usize,
62 pub end: usize,
64 pub render_start: usize,
66 pub render_end: usize,
68 pub offset: f32,
70}
71
72impl VisibleRange {
73 pub fn visible_range(&self) -> Range<usize> {
75 self.start..self.end
76 }
77
78 pub fn render_range(&self) -> Range<usize> {
80 contract_pre_render!();
81 self.render_start..self.render_end
82 }
83
84 pub fn is_visible(&self, index: usize) -> bool {
86 index >= self.start && index < self.end
87 }
88
89 pub fn should_render(&self, index: usize) -> bool {
91 contract_pre_render!();
92 index >= self.render_start && index < self.render_end
93 }
94
95 pub fn visible_count(&self) -> usize {
97 self.end.saturating_sub(self.start)
98 }
99
100 pub fn render_count(&self) -> usize {
102 contract_pre_render!();
103 self.render_end.saturating_sub(self.render_start)
104 }
105}
106
107#[derive(Debug, Clone, Copy, PartialEq)]
109pub struct ItemLayout {
110 pub y: f32,
112 pub height: f32,
114}
115
116impl ItemLayout {
117 pub fn new(y: f32, height: f32) -> Self {
118 Self { y, height }
119 }
120
121 pub fn bottom(&self) -> f32 {
123 self.y + self.height
124 }
125}
126
127#[derive(Debug)]
129pub struct VirtualList {
130 config: VirtualListConfig,
131 item_count: usize,
133 item_heights: HashMap<usize, f32>,
135 item_positions: Vec<f32>,
137 positions_dirty: bool,
139 scroll_position: f32,
141 viewport_height: f32,
143 content_height: f32,
145 visible_range: Option<VisibleRange>,
147}
148
149impl Default for VirtualList {
150 fn default() -> Self {
151 Self::new(VirtualListConfig::default())
152 }
153}
154
155impl VirtualList {
156 pub fn new(config: VirtualListConfig) -> Self {
157 let initial_scroll = config.initial_scroll;
158 Self {
159 config,
160 item_count: 0,
161 item_heights: HashMap::new(),
162 item_positions: Vec::new(),
163 positions_dirty: true,
164 scroll_position: initial_scroll,
165 viewport_height: 0.0,
166 content_height: 0.0,
167 visible_range: None,
168 }
169 }
170
171 pub fn set_item_count(&mut self, count: usize) {
173 if count != self.item_count {
174 self.item_count = count;
175 self.positions_dirty = true;
176 }
177 }
178
179 pub fn item_count(&self) -> usize {
181 self.item_count
182 }
183
184 pub fn set_viewport_height(&mut self, height: f32) {
186 if (height - self.viewport_height).abs() > 0.1 {
187 self.viewport_height = height;
188 self.update_visible_range();
189 }
190 }
191
192 pub fn viewport_height(&self) -> f32 {
194 self.viewport_height
195 }
196
197 pub fn set_scroll_position(&mut self, position: f32) {
199 let clamped = position.max(0.0).min(self.max_scroll());
200 if (clamped - self.scroll_position).abs() > 0.1 {
201 self.scroll_position = clamped;
202 self.update_visible_range();
203 }
204 }
205
206 pub fn scroll_position(&self) -> f32 {
208 self.scroll_position
209 }
210
211 pub fn max_scroll(&self) -> f32 {
213 (self.calculate_content_height() - self.viewport_height).max(0.0)
214 }
215
216 fn calculate_content_height(&self) -> f32 {
218 if !self.config.variable_heights {
219 return self.item_count as f32 * self.config.estimated_item_height;
220 }
221
222 let mut height = 0.0;
223 for i in 0..self.item_count {
224 height += self.get_item_height(i);
225 }
226 height
227 }
228
229 pub fn scroll_by(&mut self, delta: f32) {
231 self.set_scroll_position(self.scroll_position + delta);
232 }
233
234 pub fn scroll_to_item(&mut self, index: usize, align: ScrollAlign) {
236 if index >= self.item_count {
237 return;
238 }
239
240 if self.positions_dirty {
241 self.recalculate_positions();
242 }
243 let item_y = self.get_item_position(index);
244 let item_height = self.get_item_height(index);
245
246 let new_scroll = match align {
247 ScrollAlign::Start => item_y,
248 ScrollAlign::Center => item_y - (self.viewport_height - item_height) / 2.0,
249 ScrollAlign::End => item_y - self.viewport_height + item_height,
250 ScrollAlign::Auto => {
251 if item_y < self.scroll_position {
253 item_y
254 } else if item_y + item_height > self.scroll_position + self.viewport_height {
255 item_y + item_height - self.viewport_height
256 } else {
257 self.scroll_position
258 }
259 }
260 };
261
262 self.set_scroll_position(new_scroll);
263 }
264
265 pub fn set_item_height(&mut self, index: usize, height: f32) {
267 if self.config.variable_heights {
268 self.item_heights.insert(index, height);
269 self.positions_dirty = true;
270 }
271 }
272
273 pub fn get_item_height(&self, index: usize) -> f32 {
275 if self.config.variable_heights {
276 self.item_heights
277 .get(&index)
278 .copied()
279 .unwrap_or(self.config.estimated_item_height)
280 } else {
281 self.config.estimated_item_height
282 }
283 }
284
285 pub fn get_item_position(&self, index: usize) -> f32 {
287 if index == 0 {
288 return 0.0;
289 }
290
291 if !self.config.variable_heights {
293 return index as f32 * self.config.estimated_item_height;
294 }
295
296 if index < self.item_positions.len() {
298 self.item_positions[index]
299 } else {
300 let mut y = 0.0;
302 for i in 0..index {
303 y += self.get_item_height(i);
304 }
305 y
306 }
307 }
308
309 pub fn get_item_layout(&self, index: usize) -> ItemLayout {
311 ItemLayout {
312 y: self.get_item_position(index),
313 height: self.get_item_height(index),
314 }
315 }
316
317 pub fn content_height(&self) -> f32 {
319 self.calculate_content_height()
320 }
321
322 pub fn visible_range(&self) -> Option<&VisibleRange> {
324 self.visible_range.as_ref()
325 }
326
327 pub fn is_near_end(&self) -> bool {
329 self.scroll_position + self.viewport_height + self.config.load_more_threshold
330 >= self.content_height
331 }
332
333 pub fn is_near_start(&self) -> bool {
335 self.scroll_position <= self.config.load_more_threshold
336 }
337
338 fn update_visible_range(&mut self) {
340 if self.positions_dirty {
341 self.recalculate_positions();
342 }
343
344 if self.item_count == 0 || self.viewport_height <= 0.0 {
345 self.visible_range = None;
346 return;
347 }
348
349 let start = self.find_item_at_position(self.scroll_position);
351 let end = self.find_item_at_position(self.scroll_position + self.viewport_height) + 1;
352 let end = end.min(self.item_count);
353
354 let render_start = start.saturating_sub(self.config.overscan_count);
356 let render_end = (end + self.config.overscan_count).min(self.item_count);
357
358 let offset = self.get_item_position(render_start);
360
361 self.visible_range = Some(VisibleRange {
362 start,
363 end,
364 render_start,
365 render_end,
366 offset,
367 });
368 }
369
370 fn find_item_at_position(&self, position: f32) -> usize {
372 if position <= 0.0 {
373 return 0;
374 }
375
376 if !self.config.variable_heights {
377 return (position / self.config.estimated_item_height) as usize;
379 }
380
381 let mut low = 0;
383 let mut high = self.item_count;
384
385 while low < high {
386 let mid = (low + high) / 2;
387 let item_pos = self.get_item_position(mid);
388
389 if item_pos <= position {
390 low = mid + 1;
391 } else {
392 high = mid;
393 }
394 }
395
396 low.saturating_sub(1)
397 }
398
399 fn recalculate_positions(&mut self) {
401 self.item_positions.clear();
402 self.item_positions.reserve(self.item_count);
403
404 let mut current_y = 0.0;
405 for i in 0..self.item_count {
406 self.item_positions.push(current_y);
407 current_y += self.get_item_height(i);
408 }
409
410 self.content_height = current_y;
411 self.positions_dirty = false;
412 }
413
414 pub fn reset(&mut self) {
416 self.scroll_position = 0.0;
417 self.visible_range = None;
418 self.update_visible_range();
419 }
420}
421
422#[derive(Debug, Clone, Copy, PartialEq, Eq)]
424pub enum ScrollAlign {
425 Start,
427 Center,
429 End,
431 Auto,
433}
434
435#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
437pub struct GridCell {
438 pub row: usize,
439 pub col: usize,
440}
441
442impl GridCell {
443 pub fn new(row: usize, col: usize) -> Self {
444 Self { row, col }
445 }
446}
447
448#[derive(Debug, Clone)]
450pub struct VirtualGridConfig {
451 pub columns: usize,
453 pub cell_width: f32,
455 pub cell_height: f32,
457 pub gap: f32,
459 pub overscan_rows: usize,
461}
462
463impl Default for VirtualGridConfig {
464 fn default() -> Self {
465 Self {
466 columns: 3,
467 cell_width: 100.0,
468 cell_height: 100.0,
469 gap: 8.0,
470 overscan_rows: 2,
471 }
472 }
473}
474
475#[derive(Debug, Clone, PartialEq)]
477pub struct VisibleGridRange {
478 pub start_row: usize,
480 pub end_row: usize,
482 pub render_start_row: usize,
484 pub render_end_row: usize,
486 pub columns: usize,
488 pub offset: f32,
490}
491
492impl VisibleGridRange {
493 pub fn cells_to_render(&self, total_items: usize) -> Vec<GridCell> {
495 contract_pre_render!();
496 let mut cells = Vec::new();
497 for row in self.render_start_row..self.render_end_row {
498 for col in 0..self.columns {
499 let index = row * self.columns + col;
500 if index < total_items {
501 cells.push(GridCell::new(row, col));
502 }
503 }
504 }
505 cells
506 }
507
508 pub fn should_render_cell(&self, row: usize, col: usize) -> bool {
510 contract_pre_render!();
511 row >= self.render_start_row && row < self.render_end_row && col < self.columns
512 }
513}
514
515#[derive(Debug, Clone, Copy, PartialEq)]
517pub struct CellLayout {
518 pub x: f32,
519 pub y: f32,
520 pub width: f32,
521 pub height: f32,
522}
523
524impl CellLayout {
525 pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
526 Self {
527 x,
528 y,
529 width,
530 height,
531 }
532 }
533}
534
535#[derive(Debug)]
537pub struct VirtualGrid {
538 config: VirtualGridConfig,
539 item_count: usize,
541 scroll_position: f32,
543 viewport_height: f32,
545 visible_range: Option<VisibleGridRange>,
547}
548
549impl Default for VirtualGrid {
550 fn default() -> Self {
551 Self::new(VirtualGridConfig::default())
552 }
553}
554
555impl VirtualGrid {
556 pub fn new(config: VirtualGridConfig) -> Self {
557 Self {
558 config,
559 item_count: 0,
560 scroll_position: 0.0,
561 viewport_height: 0.0,
562 visible_range: None,
563 }
564 }
565
566 pub fn set_item_count(&mut self, count: usize) {
568 if count != self.item_count {
569 self.item_count = count;
570 self.update_visible_range();
571 }
572 }
573
574 pub fn item_count(&self) -> usize {
576 self.item_count
577 }
578
579 pub fn row_count(&self) -> usize {
581 self.item_count.div_ceil(self.config.columns)
582 }
583
584 pub fn set_viewport_height(&mut self, height: f32) {
586 if (height - self.viewport_height).abs() > 0.1 {
587 self.viewport_height = height;
588 self.update_visible_range();
589 }
590 }
591
592 pub fn viewport_height(&self) -> f32 {
594 self.viewport_height
595 }
596
597 pub fn set_scroll_position(&mut self, position: f32) {
599 let clamped = position.max(0.0).min(self.max_scroll());
600 if (clamped - self.scroll_position).abs() > 0.1 {
601 self.scroll_position = clamped;
602 self.update_visible_range();
603 }
604 }
605
606 pub fn scroll_position(&self) -> f32 {
608 self.scroll_position
609 }
610
611 pub fn max_scroll(&self) -> f32 {
613 (self.content_height() - self.viewport_height).max(0.0)
614 }
615
616 pub fn scroll_by(&mut self, delta: f32) {
618 self.set_scroll_position(self.scroll_position + delta);
619 }
620
621 pub fn scroll_to_item(&mut self, index: usize, align: ScrollAlign) {
623 if index >= self.item_count {
624 return;
625 }
626
627 let row = index / self.config.columns;
628 let row_y = self.row_position(row);
629
630 let new_scroll = match align {
631 ScrollAlign::Start => row_y,
632 ScrollAlign::Center => row_y - (self.viewport_height - self.row_height()) / 2.0,
633 ScrollAlign::End => row_y - self.viewport_height + self.row_height(),
634 ScrollAlign::Auto => {
635 if row_y < self.scroll_position {
636 row_y
637 } else if row_y + self.row_height() > self.scroll_position + self.viewport_height {
638 row_y + self.row_height() - self.viewport_height
639 } else {
640 self.scroll_position
641 }
642 }
643 };
644
645 self.set_scroll_position(new_scroll);
646 }
647
648 pub fn row_height(&self) -> f32 {
650 self.config.cell_height + self.config.gap
651 }
652
653 pub fn row_position(&self, row: usize) -> f32 {
655 row as f32 * self.row_height()
656 }
657
658 pub fn content_height(&self) -> f32 {
660 let rows = self.row_count();
661 if rows == 0 {
662 0.0
663 } else {
664 (rows as f32).mul_add(self.config.cell_height, (rows - 1) as f32 * self.config.gap)
665 }
666 }
667
668 pub fn get_cell_layout(&self, index: usize) -> CellLayout {
670 let row = index / self.config.columns;
671 let col = index % self.config.columns;
672 self.get_cell_layout_by_position(row, col)
673 }
674
675 pub fn get_cell_layout_by_position(&self, row: usize, col: usize) -> CellLayout {
677 let x = col as f32 * (self.config.cell_width + self.config.gap);
678 let y = row as f32 * (self.config.cell_height + self.config.gap);
679 CellLayout::new(x, y, self.config.cell_width, self.config.cell_height)
680 }
681
682 pub fn cell_to_index(&self, cell: &GridCell) -> usize {
684 cell.row * self.config.columns + cell.col
685 }
686
687 pub fn index_to_cell(&self, index: usize) -> GridCell {
689 GridCell {
690 row: index / self.config.columns,
691 col: index % self.config.columns,
692 }
693 }
694
695 pub fn visible_range(&self) -> Option<&VisibleGridRange> {
697 self.visible_range.as_ref()
698 }
699
700 fn update_visible_range(&mut self) {
702 if self.item_count == 0 || self.viewport_height <= 0.0 {
703 self.visible_range = None;
704 return;
705 }
706
707 let row_height = self.row_height();
708 let start_row = (self.scroll_position / row_height) as usize;
709 let visible_rows = (self.viewport_height / row_height).ceil() as usize + 1;
710 let end_row = (start_row + visible_rows).min(self.row_count());
711
712 let render_start_row = start_row.saturating_sub(self.config.overscan_rows);
713 let render_end_row = (end_row + self.config.overscan_rows).min(self.row_count());
714
715 let offset = render_start_row as f32 * row_height;
716
717 self.visible_range = Some(VisibleGridRange {
718 start_row,
719 end_row,
720 render_start_row,
721 render_end_row,
722 columns: self.config.columns,
723 offset,
724 });
725 }
726
727 pub fn reset(&mut self) {
729 self.scroll_position = 0.0;
730 self.visible_range = None;
731 self.update_visible_range();
732 }
733}
734
735#[cfg(test)]
736#[allow(clippy::unwrap_used, clippy::disallowed_methods)]
737mod tests {
738 use super::*;
739
740 #[test]
741 fn test_virtual_list_default() {
742 let list = VirtualList::default();
743 assert_eq!(list.item_count(), 0);
744 assert_eq!(list.scroll_position(), 0.0);
745 }
746
747 #[test]
748 fn test_virtual_list_set_item_count() {
749 let mut list = VirtualList::default();
750 list.set_item_count(100);
751 assert_eq!(list.item_count(), 100);
752 }
753
754 #[test]
755 fn test_virtual_list_viewport() {
756 let mut list = VirtualList::default();
757 list.set_viewport_height(500.0);
758 assert_eq!(list.viewport_height(), 500.0);
759 }
760
761 #[test]
762 fn test_virtual_list_scroll_position() {
763 let mut list = VirtualList::default();
764 list.set_item_count(100);
765 list.set_viewport_height(500.0);
766
767 list.set_scroll_position(100.0);
768 assert_eq!(list.scroll_position(), 100.0);
769 }
770
771 #[test]
772 fn test_virtual_list_scroll_clamped() {
773 let mut list = VirtualList::default();
774 list.set_item_count(10);
775 list.set_viewport_height(500.0);
776
777 list.set_scroll_position(-100.0);
779 assert_eq!(list.scroll_position(), 0.0);
780 }
781
782 #[test]
783 fn test_virtual_list_scroll_by() {
784 let mut list = VirtualList::default();
785 list.set_item_count(100);
786 list.set_viewport_height(500.0);
787
788 list.scroll_by(50.0);
789 assert_eq!(list.scroll_position(), 50.0);
790
791 list.scroll_by(25.0);
792 assert_eq!(list.scroll_position(), 75.0);
793 }
794
795 #[test]
796 fn test_virtual_list_content_height() {
797 let config = VirtualListConfig {
798 estimated_item_height: 40.0,
799 ..Default::default()
800 };
801 let mut list = VirtualList::new(config);
802 list.set_item_count(10);
803
804 assert_eq!(list.content_height(), 400.0);
805 }
806
807 #[test]
808 fn test_virtual_list_max_scroll() {
809 let config = VirtualListConfig {
810 estimated_item_height: 50.0,
811 ..Default::default()
812 };
813 let mut list = VirtualList::new(config);
814 list.set_item_count(20);
815 list.set_viewport_height(400.0);
816
817 assert_eq!(list.max_scroll(), 600.0);
819 }
820
821 #[test]
822 fn test_virtual_list_visible_range() {
823 let config = VirtualListConfig {
824 estimated_item_height: 50.0,
825 overscan_count: 2,
826 ..Default::default()
827 };
828 let mut list = VirtualList::new(config);
829 list.set_item_count(100);
830 list.set_viewport_height(200.0);
831
832 let range = list.visible_range().unwrap();
833 assert_eq!(range.start, 0);
836 assert_eq!(range.end, 5);
837 assert_eq!(range.render_start, 0);
838 assert_eq!(range.render_end, 7); }
840
841 #[test]
842 fn test_virtual_list_visible_range_scrolled() {
843 let config = VirtualListConfig {
844 estimated_item_height: 50.0,
845 overscan_count: 2,
846 ..Default::default()
847 };
848 let mut list = VirtualList::new(config);
849 list.set_item_count(100);
850 list.set_viewport_height(200.0);
851 list.set_scroll_position(250.0);
852
853 let range = list.visible_range().unwrap();
854 assert_eq!(range.start, 5);
856 assert_eq!(range.end, 10);
857 assert_eq!(range.render_start, 3); assert_eq!(range.render_end, 12); }
860
861 #[test]
862 fn test_virtual_list_scroll_to_item_start() {
863 let config = VirtualListConfig {
864 estimated_item_height: 50.0,
865 ..Default::default()
866 };
867 let mut list = VirtualList::new(config);
868 list.set_item_count(100);
869 list.set_viewport_height(200.0);
870
871 list.scroll_to_item(10, ScrollAlign::Start);
872 assert_eq!(list.scroll_position(), 500.0);
873 }
874
875 #[test]
876 fn test_virtual_list_scroll_to_item_center() {
877 let config = VirtualListConfig {
878 estimated_item_height: 50.0,
879 ..Default::default()
880 };
881 let mut list = VirtualList::new(config);
882 list.set_item_count(100);
883 list.set_viewport_height(200.0);
884
885 list.scroll_to_item(10, ScrollAlign::Center);
886 assert_eq!(list.scroll_position(), 425.0);
889 }
890
891 #[test]
892 fn test_virtual_list_scroll_to_item_end() {
893 let config = VirtualListConfig {
894 estimated_item_height: 50.0,
895 ..Default::default()
896 };
897 let mut list = VirtualList::new(config);
898 list.set_item_count(100);
899 list.set_viewport_height(200.0);
900
901 list.scroll_to_item(10, ScrollAlign::End);
902 assert_eq!(list.scroll_position(), 350.0);
905 }
906
907 #[test]
908 fn test_virtual_list_scroll_to_item_auto() {
909 let config = VirtualListConfig {
910 estimated_item_height: 50.0,
911 ..Default::default()
912 };
913 let mut list = VirtualList::new(config);
914 list.set_item_count(100);
915 list.set_viewport_height(200.0);
916
917 list.scroll_to_item(2, ScrollAlign::Auto);
919 assert_eq!(list.scroll_position(), 0.0);
920
921 list.scroll_to_item(10, ScrollAlign::Auto);
923 assert!(list.scroll_position() > 0.0);
924 }
925
926 #[test]
927 fn test_virtual_list_variable_heights() {
928 let config = VirtualListConfig {
929 estimated_item_height: 50.0,
930 variable_heights: true,
931 ..Default::default()
932 };
933 let mut list = VirtualList::new(config);
934 list.set_item_count(10);
935
936 list.set_item_height(2, 100.0);
937 assert_eq!(list.get_item_height(2), 100.0);
938 assert_eq!(list.get_item_height(3), 50.0); }
940
941 #[test]
942 fn test_virtual_list_item_layout() {
943 let config = VirtualListConfig {
944 estimated_item_height: 50.0,
945 ..Default::default()
946 };
947 let mut list = VirtualList::new(config);
948 list.set_item_count(10);
949
950 let layout = list.get_item_layout(5);
951 assert_eq!(layout.y, 250.0);
952 assert_eq!(layout.height, 50.0);
953 }
954
955 #[test]
956 fn test_virtual_list_is_near_end() {
957 let config = VirtualListConfig {
958 estimated_item_height: 50.0,
959 load_more_threshold: 100.0,
960 ..Default::default()
961 };
962 let mut list = VirtualList::new(config);
963 list.set_item_count(20); list.set_viewport_height(300.0);
965
966 assert!(!list.is_near_end());
967
968 list.set_scroll_position(600.0); assert!(list.is_near_end());
970 }
971
972 #[test]
973 fn test_virtual_list_is_near_start() {
974 let config = VirtualListConfig {
975 load_more_threshold: 100.0,
976 ..Default::default()
977 };
978 let mut list = VirtualList::new(config);
979 list.set_item_count(100);
980 list.set_viewport_height(300.0);
981
982 assert!(list.is_near_start());
983
984 list.set_scroll_position(200.0);
985 assert!(!list.is_near_start());
986 }
987
988 #[test]
989 fn test_virtual_list_reset() {
990 let mut list = VirtualList::default();
991 list.set_item_count(100);
992 list.set_viewport_height(300.0);
993 list.set_scroll_position(500.0);
994
995 list.reset();
996 assert_eq!(list.scroll_position(), 0.0);
997 }
998
999 #[test]
1000 fn test_visible_range_methods() {
1001 let range = VisibleRange {
1002 start: 5,
1003 end: 10,
1004 render_start: 3,
1005 render_end: 12,
1006 offset: 150.0,
1007 };
1008
1009 assert_eq!(range.visible_range(), 5..10);
1010 assert_eq!(range.render_range(), 3..12);
1011 assert_eq!(range.visible_count(), 5);
1012 assert_eq!(range.render_count(), 9);
1013 assert!(range.is_visible(7));
1014 assert!(!range.is_visible(2));
1015 assert!(range.should_render(5));
1016 assert!(!range.should_render(15));
1017 }
1018
1019 #[test]
1020 fn test_item_layout() {
1021 let layout = ItemLayout::new(100.0, 50.0);
1022 assert_eq!(layout.y, 100.0);
1023 assert_eq!(layout.height, 50.0);
1024 assert_eq!(layout.bottom(), 150.0);
1025 }
1026
1027 #[test]
1028 fn test_item_index() {
1029 let index = ItemIndex(42);
1030 assert_eq!(index.as_usize(), 42);
1031
1032 let from_usize: ItemIndex = 100.into();
1033 assert_eq!(from_usize.0, 100);
1034 }
1035
1036 #[test]
1039 fn test_virtual_grid_default() {
1040 let grid = VirtualGrid::default();
1041 assert_eq!(grid.item_count(), 0);
1042 assert_eq!(grid.scroll_position(), 0.0);
1043 }
1044
1045 #[test]
1046 fn test_virtual_grid_set_item_count() {
1047 let mut grid = VirtualGrid::default();
1048 grid.set_item_count(100);
1049 assert_eq!(grid.item_count(), 100);
1050 }
1051
1052 #[test]
1053 fn test_virtual_grid_row_count() {
1054 let config = VirtualGridConfig {
1055 columns: 3,
1056 ..Default::default()
1057 };
1058 let mut grid = VirtualGrid::new(config);
1059 grid.set_item_count(10);
1060 assert_eq!(grid.row_count(), 4); }
1062
1063 #[test]
1064 fn test_virtual_grid_viewport() {
1065 let mut grid = VirtualGrid::default();
1066 grid.set_viewport_height(500.0);
1067 assert_eq!(grid.viewport_height(), 500.0);
1068 }
1069
1070 #[test]
1071 fn test_virtual_grid_scroll_position() {
1072 let mut grid = VirtualGrid::default();
1073 grid.set_item_count(100);
1074 grid.set_viewport_height(500.0);
1075
1076 grid.set_scroll_position(200.0);
1077 assert_eq!(grid.scroll_position(), 200.0);
1078 }
1079
1080 #[test]
1081 fn test_virtual_grid_content_height() {
1082 let config = VirtualGridConfig {
1083 columns: 3,
1084 cell_height: 100.0,
1085 gap: 10.0,
1086 ..Default::default()
1087 };
1088 let mut grid = VirtualGrid::new(config);
1089 grid.set_item_count(9); assert_eq!(grid.content_height(), 320.0);
1093 }
1094
1095 #[test]
1096 fn test_virtual_grid_cell_layout() {
1097 let config = VirtualGridConfig {
1098 columns: 3,
1099 cell_width: 100.0,
1100 cell_height: 80.0,
1101 gap: 10.0,
1102 ..Default::default()
1103 };
1104 let grid = VirtualGrid::new(config);
1105
1106 let layout = grid.get_cell_layout(0);
1108 assert_eq!(layout.x, 0.0);
1109 assert_eq!(layout.y, 0.0);
1110
1111 let layout = grid.get_cell_layout(1);
1113 assert_eq!(layout.x, 110.0);
1114 assert_eq!(layout.y, 0.0);
1115
1116 let layout = grid.get_cell_layout(3);
1118 assert_eq!(layout.x, 0.0);
1119 assert_eq!(layout.y, 90.0);
1120 }
1121
1122 #[test]
1123 fn test_virtual_grid_cell_conversion() {
1124 let config = VirtualGridConfig {
1125 columns: 4,
1126 ..Default::default()
1127 };
1128 let grid = VirtualGrid::new(config);
1129
1130 let cell = grid.index_to_cell(10);
1131 assert_eq!(cell.row, 2);
1132 assert_eq!(cell.col, 2);
1133
1134 assert_eq!(grid.cell_to_index(&cell), 10);
1135 }
1136
1137 #[test]
1138 fn test_virtual_grid_visible_range() {
1139 let config = VirtualGridConfig {
1140 columns: 3,
1141 cell_height: 100.0,
1142 gap: 10.0,
1143 overscan_rows: 1,
1144 ..Default::default()
1145 };
1146 let mut grid = VirtualGrid::new(config);
1147 grid.set_item_count(30); grid.set_viewport_height(250.0);
1149
1150 let range = grid.visible_range().unwrap();
1151 assert_eq!(range.start_row, 0);
1154 assert!(range.end_row >= 2);
1155 }
1156
1157 #[test]
1158 fn test_virtual_grid_scroll_to_item() {
1159 let config = VirtualGridConfig {
1160 columns: 3,
1161 cell_height: 100.0,
1162 gap: 10.0,
1163 ..Default::default()
1164 };
1165 let mut grid = VirtualGrid::new(config);
1166 grid.set_item_count(30);
1167 grid.set_viewport_height(250.0);
1168
1169 grid.scroll_to_item(15, ScrollAlign::Start); assert_eq!(grid.scroll_position(), 550.0); }
1172
1173 #[test]
1174 fn test_virtual_grid_reset() {
1175 let mut grid = VirtualGrid::default();
1176 grid.set_item_count(100);
1177 grid.set_viewport_height(300.0);
1178 grid.set_scroll_position(500.0);
1179
1180 grid.reset();
1181 assert_eq!(grid.scroll_position(), 0.0);
1182 }
1183
1184 #[test]
1185 fn test_grid_cell() {
1186 let cell = GridCell::new(5, 2);
1187 assert_eq!(cell.row, 5);
1188 assert_eq!(cell.col, 2);
1189 }
1190
1191 #[test]
1192 fn test_visible_grid_range_cells() {
1193 let range = VisibleGridRange {
1194 start_row: 2,
1195 end_row: 5,
1196 render_start_row: 1,
1197 render_end_row: 6,
1198 columns: 3,
1199 offset: 100.0,
1200 };
1201
1202 let cells = range.cells_to_render(100);
1204 assert_eq!(cells.len(), 15);
1205
1206 let cells = range.cells_to_render(10);
1209 assert_eq!(cells.len(), 7);
1210 }
1211
1212 #[test]
1213 fn test_visible_grid_range_should_render() {
1214 let range = VisibleGridRange {
1215 start_row: 2,
1216 end_row: 5,
1217 render_start_row: 1,
1218 render_end_row: 6,
1219 columns: 3,
1220 offset: 100.0,
1221 };
1222
1223 assert!(range.should_render_cell(3, 1));
1224 assert!(!range.should_render_cell(0, 0));
1225 assert!(!range.should_render_cell(3, 5)); }
1227
1228 #[test]
1229 fn test_cell_layout() {
1230 let layout = CellLayout::new(100.0, 200.0, 50.0, 60.0);
1231 assert_eq!(layout.x, 100.0);
1232 assert_eq!(layout.y, 200.0);
1233 assert_eq!(layout.width, 50.0);
1234 assert_eq!(layout.height, 60.0);
1235 }
1236
1237 #[test]
1238 fn test_scroll_align_variants() {
1239 assert_ne!(ScrollAlign::Start, ScrollAlign::End);
1241 assert_ne!(ScrollAlign::Center, ScrollAlign::Auto);
1242 }
1243
1244 #[test]
1245 fn test_virtual_list_empty() {
1246 let mut list = VirtualList::default();
1247 list.set_viewport_height(300.0);
1248 assert!(list.visible_range().is_none());
1250 }
1251
1252 #[test]
1253 fn test_virtual_grid_empty() {
1254 let mut grid = VirtualGrid::default();
1255 grid.set_viewport_height(300.0);
1256 assert!(grid.visible_range().is_none());
1258 }
1259
1260 #[test]
1261 fn test_virtual_list_config_default() {
1262 let config = VirtualListConfig::default();
1263 assert_eq!(config.estimated_item_height, 50.0);
1264 assert_eq!(config.overscan_count, 3);
1265 assert!(!config.variable_heights);
1266 assert_eq!(config.initial_scroll, 0.0);
1267 assert_eq!(config.load_more_threshold, 100.0);
1268 }
1269
1270 #[test]
1271 fn test_virtual_grid_config_default() {
1272 let config = VirtualGridConfig::default();
1273 assert_eq!(config.columns, 3);
1274 assert_eq!(config.cell_width, 100.0);
1275 assert_eq!(config.cell_height, 100.0);
1276 assert_eq!(config.gap, 8.0);
1277 assert_eq!(config.overscan_rows, 2);
1278 }
1279}