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
127pub struct VirtualList {
129 config: VirtualListConfig,
130 item_count: usize,
132 item_heights: HashMap<usize, f32>,
134 item_positions: Vec<f32>,
136 positions_dirty: bool,
138 scroll_position: f32,
140 viewport_height: f32,
142 content_height: f32,
144 visible_range: Option<VisibleRange>,
146}
147
148impl Default for VirtualList {
149 fn default() -> Self {
150 Self::new(VirtualListConfig::default())
151 }
152}
153
154impl VirtualList {
155 pub fn new(config: VirtualListConfig) -> Self {
156 let initial_scroll = config.initial_scroll;
157 Self {
158 config,
159 item_count: 0,
160 item_heights: HashMap::new(),
161 item_positions: Vec::new(),
162 positions_dirty: true,
163 scroll_position: initial_scroll,
164 viewport_height: 0.0,
165 content_height: 0.0,
166 visible_range: None,
167 }
168 }
169
170 pub fn set_item_count(&mut self, count: usize) {
172 if count != self.item_count {
173 self.item_count = count;
174 self.positions_dirty = true;
175 }
176 }
177
178 pub fn item_count(&self) -> usize {
180 self.item_count
181 }
182
183 pub fn set_viewport_height(&mut self, height: f32) {
185 if (height - self.viewport_height).abs() > 0.1 {
186 self.viewport_height = height;
187 self.update_visible_range();
188 }
189 }
190
191 pub fn viewport_height(&self) -> f32 {
193 self.viewport_height
194 }
195
196 pub fn set_scroll_position(&mut self, position: f32) {
198 let clamped = position.max(0.0).min(self.max_scroll());
199 if (clamped - self.scroll_position).abs() > 0.1 {
200 self.scroll_position = clamped;
201 self.update_visible_range();
202 }
203 }
204
205 pub fn scroll_position(&self) -> f32 {
207 self.scroll_position
208 }
209
210 pub fn max_scroll(&self) -> f32 {
212 (self.calculate_content_height() - self.viewport_height).max(0.0)
213 }
214
215 fn calculate_content_height(&self) -> f32 {
217 if !self.config.variable_heights {
218 return self.item_count as f32 * self.config.estimated_item_height;
219 }
220
221 let mut height = 0.0;
222 for i in 0..self.item_count {
223 height += self.get_item_height(i);
224 }
225 height
226 }
227
228 pub fn scroll_by(&mut self, delta: f32) {
230 self.set_scroll_position(self.scroll_position + delta);
231 }
232
233 pub fn scroll_to_item(&mut self, index: usize, align: ScrollAlign) {
235 if index >= self.item_count {
236 return;
237 }
238
239 if self.positions_dirty {
240 self.recalculate_positions();
241 }
242 let item_y = self.get_item_position(index);
243 let item_height = self.get_item_height(index);
244
245 let new_scroll = match align {
246 ScrollAlign::Start => item_y,
247 ScrollAlign::Center => item_y - (self.viewport_height - item_height) / 2.0,
248 ScrollAlign::End => item_y - self.viewport_height + item_height,
249 ScrollAlign::Auto => {
250 if item_y < self.scroll_position {
252 item_y
253 } else if item_y + item_height > self.scroll_position + self.viewport_height {
254 item_y + item_height - self.viewport_height
255 } else {
256 self.scroll_position
257 }
258 }
259 };
260
261 self.set_scroll_position(new_scroll);
262 }
263
264 pub fn set_item_height(&mut self, index: usize, height: f32) {
266 if self.config.variable_heights {
267 self.item_heights.insert(index, height);
268 self.positions_dirty = true;
269 }
270 }
271
272 pub fn get_item_height(&self, index: usize) -> f32 {
274 if self.config.variable_heights {
275 self.item_heights
276 .get(&index)
277 .copied()
278 .unwrap_or(self.config.estimated_item_height)
279 } else {
280 self.config.estimated_item_height
281 }
282 }
283
284 pub fn get_item_position(&self, index: usize) -> f32 {
286 if index == 0 {
287 return 0.0;
288 }
289
290 if !self.config.variable_heights {
292 return index as f32 * self.config.estimated_item_height;
293 }
294
295 if index < self.item_positions.len() {
297 self.item_positions[index]
298 } else {
299 let mut y = 0.0;
301 for i in 0..index {
302 y += self.get_item_height(i);
303 }
304 y
305 }
306 }
307
308 pub fn get_item_layout(&self, index: usize) -> ItemLayout {
310 ItemLayout {
311 y: self.get_item_position(index),
312 height: self.get_item_height(index),
313 }
314 }
315
316 pub fn content_height(&self) -> f32 {
318 self.calculate_content_height()
319 }
320
321 pub fn visible_range(&self) -> Option<&VisibleRange> {
323 self.visible_range.as_ref()
324 }
325
326 pub fn is_near_end(&self) -> bool {
328 self.scroll_position + self.viewport_height + self.config.load_more_threshold
329 >= self.content_height
330 }
331
332 pub fn is_near_start(&self) -> bool {
334 self.scroll_position <= self.config.load_more_threshold
335 }
336
337 fn update_visible_range(&mut self) {
339 if self.positions_dirty {
340 self.recalculate_positions();
341 }
342
343 if self.item_count == 0 || self.viewport_height <= 0.0 {
344 self.visible_range = None;
345 return;
346 }
347
348 let start = self.find_item_at_position(self.scroll_position);
350 let end = self.find_item_at_position(self.scroll_position + self.viewport_height) + 1;
351 let end = end.min(self.item_count);
352
353 let render_start = start.saturating_sub(self.config.overscan_count);
355 let render_end = (end + self.config.overscan_count).min(self.item_count);
356
357 let offset = self.get_item_position(render_start);
359
360 self.visible_range = Some(VisibleRange {
361 start,
362 end,
363 render_start,
364 render_end,
365 offset,
366 });
367 }
368
369 fn find_item_at_position(&self, position: f32) -> usize {
371 if position <= 0.0 {
372 return 0;
373 }
374
375 if !self.config.variable_heights {
376 return (position / self.config.estimated_item_height) as usize;
378 }
379
380 let mut low = 0;
382 let mut high = self.item_count;
383
384 while low < high {
385 let mid = (low + high) / 2;
386 let item_pos = self.get_item_position(mid);
387
388 if item_pos <= position {
389 low = mid + 1;
390 } else {
391 high = mid;
392 }
393 }
394
395 low.saturating_sub(1)
396 }
397
398 fn recalculate_positions(&mut self) {
400 self.item_positions.clear();
401 self.item_positions.reserve(self.item_count);
402
403 let mut current_y = 0.0;
404 for i in 0..self.item_count {
405 self.item_positions.push(current_y);
406 current_y += self.get_item_height(i);
407 }
408
409 self.content_height = current_y;
410 self.positions_dirty = false;
411 }
412
413 pub fn reset(&mut self) {
415 self.scroll_position = 0.0;
416 self.visible_range = None;
417 self.update_visible_range();
418 }
419}
420
421#[derive(Debug, Clone, Copy, PartialEq, Eq)]
423pub enum ScrollAlign {
424 Start,
426 Center,
428 End,
430 Auto,
432}
433
434#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
436pub struct GridCell {
437 pub row: usize,
438 pub col: usize,
439}
440
441impl GridCell {
442 pub fn new(row: usize, col: usize) -> Self {
443 Self { row, col }
444 }
445}
446
447#[derive(Debug, Clone)]
449pub struct VirtualGridConfig {
450 pub columns: usize,
452 pub cell_width: f32,
454 pub cell_height: f32,
456 pub gap: f32,
458 pub overscan_rows: usize,
460}
461
462impl Default for VirtualGridConfig {
463 fn default() -> Self {
464 Self {
465 columns: 3,
466 cell_width: 100.0,
467 cell_height: 100.0,
468 gap: 8.0,
469 overscan_rows: 2,
470 }
471 }
472}
473
474#[derive(Debug, Clone, PartialEq)]
476pub struct VisibleGridRange {
477 pub start_row: usize,
479 pub end_row: usize,
481 pub render_start_row: usize,
483 pub render_end_row: usize,
485 pub columns: usize,
487 pub offset: f32,
489}
490
491impl VisibleGridRange {
492 pub fn cells_to_render(&self, total_items: usize) -> Vec<GridCell> {
494 contract_pre_render!();
495 let mut cells = Vec::new();
496 for row in self.render_start_row..self.render_end_row {
497 for col in 0..self.columns {
498 let index = row * self.columns + col;
499 if index < total_items {
500 cells.push(GridCell::new(row, col));
501 }
502 }
503 }
504 cells
505 }
506
507 pub fn should_render_cell(&self, row: usize, col: usize) -> bool {
509 contract_pre_render!();
510 row >= self.render_start_row && row < self.render_end_row && col < self.columns
511 }
512}
513
514#[derive(Debug, Clone, Copy, PartialEq)]
516pub struct CellLayout {
517 pub x: f32,
518 pub y: f32,
519 pub width: f32,
520 pub height: f32,
521}
522
523impl CellLayout {
524 pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
525 Self {
526 x,
527 y,
528 width,
529 height,
530 }
531 }
532}
533
534pub struct VirtualGrid {
536 config: VirtualGridConfig,
537 item_count: usize,
539 scroll_position: f32,
541 viewport_height: f32,
543 visible_range: Option<VisibleGridRange>,
545}
546
547impl Default for VirtualGrid {
548 fn default() -> Self {
549 Self::new(VirtualGridConfig::default())
550 }
551}
552
553impl VirtualGrid {
554 pub fn new(config: VirtualGridConfig) -> Self {
555 Self {
556 config,
557 item_count: 0,
558 scroll_position: 0.0,
559 viewport_height: 0.0,
560 visible_range: None,
561 }
562 }
563
564 pub fn set_item_count(&mut self, count: usize) {
566 if count != self.item_count {
567 self.item_count = count;
568 self.update_visible_range();
569 }
570 }
571
572 pub fn item_count(&self) -> usize {
574 self.item_count
575 }
576
577 pub fn row_count(&self) -> usize {
579 self.item_count.div_ceil(self.config.columns)
580 }
581
582 pub fn set_viewport_height(&mut self, height: f32) {
584 if (height - self.viewport_height).abs() > 0.1 {
585 self.viewport_height = height;
586 self.update_visible_range();
587 }
588 }
589
590 pub fn viewport_height(&self) -> f32 {
592 self.viewport_height
593 }
594
595 pub fn set_scroll_position(&mut self, position: f32) {
597 let clamped = position.max(0.0).min(self.max_scroll());
598 if (clamped - self.scroll_position).abs() > 0.1 {
599 self.scroll_position = clamped;
600 self.update_visible_range();
601 }
602 }
603
604 pub fn scroll_position(&self) -> f32 {
606 self.scroll_position
607 }
608
609 pub fn max_scroll(&self) -> f32 {
611 (self.content_height() - self.viewport_height).max(0.0)
612 }
613
614 pub fn scroll_by(&mut self, delta: f32) {
616 self.set_scroll_position(self.scroll_position + delta);
617 }
618
619 pub fn scroll_to_item(&mut self, index: usize, align: ScrollAlign) {
621 if index >= self.item_count {
622 return;
623 }
624
625 let row = index / self.config.columns;
626 let row_y = self.row_position(row);
627
628 let new_scroll = match align {
629 ScrollAlign::Start => row_y,
630 ScrollAlign::Center => row_y - (self.viewport_height - self.row_height()) / 2.0,
631 ScrollAlign::End => row_y - self.viewport_height + self.row_height(),
632 ScrollAlign::Auto => {
633 if row_y < self.scroll_position {
634 row_y
635 } else if row_y + self.row_height() > self.scroll_position + self.viewport_height {
636 row_y + self.row_height() - self.viewport_height
637 } else {
638 self.scroll_position
639 }
640 }
641 };
642
643 self.set_scroll_position(new_scroll);
644 }
645
646 pub fn row_height(&self) -> f32 {
648 self.config.cell_height + self.config.gap
649 }
650
651 pub fn row_position(&self, row: usize) -> f32 {
653 row as f32 * self.row_height()
654 }
655
656 pub fn content_height(&self) -> f32 {
658 let rows = self.row_count();
659 if rows == 0 {
660 0.0
661 } else {
662 (rows as f32).mul_add(self.config.cell_height, (rows - 1) as f32 * self.config.gap)
663 }
664 }
665
666 pub fn get_cell_layout(&self, index: usize) -> CellLayout {
668 let row = index / self.config.columns;
669 let col = index % self.config.columns;
670 self.get_cell_layout_by_position(row, col)
671 }
672
673 pub fn get_cell_layout_by_position(&self, row: usize, col: usize) -> CellLayout {
675 let x = col as f32 * (self.config.cell_width + self.config.gap);
676 let y = row as f32 * (self.config.cell_height + self.config.gap);
677 CellLayout::new(x, y, self.config.cell_width, self.config.cell_height)
678 }
679
680 pub fn cell_to_index(&self, cell: &GridCell) -> usize {
682 cell.row * self.config.columns + cell.col
683 }
684
685 pub fn index_to_cell(&self, index: usize) -> GridCell {
687 GridCell {
688 row: index / self.config.columns,
689 col: index % self.config.columns,
690 }
691 }
692
693 pub fn visible_range(&self) -> Option<&VisibleGridRange> {
695 self.visible_range.as_ref()
696 }
697
698 fn update_visible_range(&mut self) {
700 if self.item_count == 0 || self.viewport_height <= 0.0 {
701 self.visible_range = None;
702 return;
703 }
704
705 let row_height = self.row_height();
706 let start_row = (self.scroll_position / row_height) as usize;
707 let visible_rows = (self.viewport_height / row_height).ceil() as usize + 1;
708 let end_row = (start_row + visible_rows).min(self.row_count());
709
710 let render_start_row = start_row.saturating_sub(self.config.overscan_rows);
711 let render_end_row = (end_row + self.config.overscan_rows).min(self.row_count());
712
713 let offset = render_start_row as f32 * row_height;
714
715 self.visible_range = Some(VisibleGridRange {
716 start_row,
717 end_row,
718 render_start_row,
719 render_end_row,
720 columns: self.config.columns,
721 offset,
722 });
723 }
724
725 pub fn reset(&mut self) {
727 self.scroll_position = 0.0;
728 self.visible_range = None;
729 self.update_visible_range();
730 }
731}
732
733#[cfg(test)]
734#[allow(clippy::unwrap_used, clippy::disallowed_methods)]
735mod tests {
736 use super::*;
737
738 #[test]
739 fn test_virtual_list_default() {
740 let list = VirtualList::default();
741 assert_eq!(list.item_count(), 0);
742 assert_eq!(list.scroll_position(), 0.0);
743 }
744
745 #[test]
746 fn test_virtual_list_set_item_count() {
747 let mut list = VirtualList::default();
748 list.set_item_count(100);
749 assert_eq!(list.item_count(), 100);
750 }
751
752 #[test]
753 fn test_virtual_list_viewport() {
754 let mut list = VirtualList::default();
755 list.set_viewport_height(500.0);
756 assert_eq!(list.viewport_height(), 500.0);
757 }
758
759 #[test]
760 fn test_virtual_list_scroll_position() {
761 let mut list = VirtualList::default();
762 list.set_item_count(100);
763 list.set_viewport_height(500.0);
764
765 list.set_scroll_position(100.0);
766 assert_eq!(list.scroll_position(), 100.0);
767 }
768
769 #[test]
770 fn test_virtual_list_scroll_clamped() {
771 let mut list = VirtualList::default();
772 list.set_item_count(10);
773 list.set_viewport_height(500.0);
774
775 list.set_scroll_position(-100.0);
777 assert_eq!(list.scroll_position(), 0.0);
778 }
779
780 #[test]
781 fn test_virtual_list_scroll_by() {
782 let mut list = VirtualList::default();
783 list.set_item_count(100);
784 list.set_viewport_height(500.0);
785
786 list.scroll_by(50.0);
787 assert_eq!(list.scroll_position(), 50.0);
788
789 list.scroll_by(25.0);
790 assert_eq!(list.scroll_position(), 75.0);
791 }
792
793 #[test]
794 fn test_virtual_list_content_height() {
795 let config = VirtualListConfig {
796 estimated_item_height: 40.0,
797 ..Default::default()
798 };
799 let mut list = VirtualList::new(config);
800 list.set_item_count(10);
801
802 assert_eq!(list.content_height(), 400.0);
803 }
804
805 #[test]
806 fn test_virtual_list_max_scroll() {
807 let config = VirtualListConfig {
808 estimated_item_height: 50.0,
809 ..Default::default()
810 };
811 let mut list = VirtualList::new(config);
812 list.set_item_count(20);
813 list.set_viewport_height(400.0);
814
815 assert_eq!(list.max_scroll(), 600.0);
817 }
818
819 #[test]
820 fn test_virtual_list_visible_range() {
821 let config = VirtualListConfig {
822 estimated_item_height: 50.0,
823 overscan_count: 2,
824 ..Default::default()
825 };
826 let mut list = VirtualList::new(config);
827 list.set_item_count(100);
828 list.set_viewport_height(200.0);
829
830 let range = list.visible_range().unwrap();
831 assert_eq!(range.start, 0);
834 assert_eq!(range.end, 5);
835 assert_eq!(range.render_start, 0);
836 assert_eq!(range.render_end, 7); }
838
839 #[test]
840 fn test_virtual_list_visible_range_scrolled() {
841 let config = VirtualListConfig {
842 estimated_item_height: 50.0,
843 overscan_count: 2,
844 ..Default::default()
845 };
846 let mut list = VirtualList::new(config);
847 list.set_item_count(100);
848 list.set_viewport_height(200.0);
849 list.set_scroll_position(250.0);
850
851 let range = list.visible_range().unwrap();
852 assert_eq!(range.start, 5);
854 assert_eq!(range.end, 10);
855 assert_eq!(range.render_start, 3); assert_eq!(range.render_end, 12); }
858
859 #[test]
860 fn test_virtual_list_scroll_to_item_start() {
861 let config = VirtualListConfig {
862 estimated_item_height: 50.0,
863 ..Default::default()
864 };
865 let mut list = VirtualList::new(config);
866 list.set_item_count(100);
867 list.set_viewport_height(200.0);
868
869 list.scroll_to_item(10, ScrollAlign::Start);
870 assert_eq!(list.scroll_position(), 500.0);
871 }
872
873 #[test]
874 fn test_virtual_list_scroll_to_item_center() {
875 let config = VirtualListConfig {
876 estimated_item_height: 50.0,
877 ..Default::default()
878 };
879 let mut list = VirtualList::new(config);
880 list.set_item_count(100);
881 list.set_viewport_height(200.0);
882
883 list.scroll_to_item(10, ScrollAlign::Center);
884 assert_eq!(list.scroll_position(), 425.0);
887 }
888
889 #[test]
890 fn test_virtual_list_scroll_to_item_end() {
891 let config = VirtualListConfig {
892 estimated_item_height: 50.0,
893 ..Default::default()
894 };
895 let mut list = VirtualList::new(config);
896 list.set_item_count(100);
897 list.set_viewport_height(200.0);
898
899 list.scroll_to_item(10, ScrollAlign::End);
900 assert_eq!(list.scroll_position(), 350.0);
903 }
904
905 #[test]
906 fn test_virtual_list_scroll_to_item_auto() {
907 let config = VirtualListConfig {
908 estimated_item_height: 50.0,
909 ..Default::default()
910 };
911 let mut list = VirtualList::new(config);
912 list.set_item_count(100);
913 list.set_viewport_height(200.0);
914
915 list.scroll_to_item(2, ScrollAlign::Auto);
917 assert_eq!(list.scroll_position(), 0.0);
918
919 list.scroll_to_item(10, ScrollAlign::Auto);
921 assert!(list.scroll_position() > 0.0);
922 }
923
924 #[test]
925 fn test_virtual_list_variable_heights() {
926 let config = VirtualListConfig {
927 estimated_item_height: 50.0,
928 variable_heights: true,
929 ..Default::default()
930 };
931 let mut list = VirtualList::new(config);
932 list.set_item_count(10);
933
934 list.set_item_height(2, 100.0);
935 assert_eq!(list.get_item_height(2), 100.0);
936 assert_eq!(list.get_item_height(3), 50.0); }
938
939 #[test]
940 fn test_virtual_list_item_layout() {
941 let config = VirtualListConfig {
942 estimated_item_height: 50.0,
943 ..Default::default()
944 };
945 let mut list = VirtualList::new(config);
946 list.set_item_count(10);
947
948 let layout = list.get_item_layout(5);
949 assert_eq!(layout.y, 250.0);
950 assert_eq!(layout.height, 50.0);
951 }
952
953 #[test]
954 fn test_virtual_list_is_near_end() {
955 let config = VirtualListConfig {
956 estimated_item_height: 50.0,
957 load_more_threshold: 100.0,
958 ..Default::default()
959 };
960 let mut list = VirtualList::new(config);
961 list.set_item_count(20); list.set_viewport_height(300.0);
963
964 assert!(!list.is_near_end());
965
966 list.set_scroll_position(600.0); assert!(list.is_near_end());
968 }
969
970 #[test]
971 fn test_virtual_list_is_near_start() {
972 let config = VirtualListConfig {
973 load_more_threshold: 100.0,
974 ..Default::default()
975 };
976 let mut list = VirtualList::new(config);
977 list.set_item_count(100);
978 list.set_viewport_height(300.0);
979
980 assert!(list.is_near_start());
981
982 list.set_scroll_position(200.0);
983 assert!(!list.is_near_start());
984 }
985
986 #[test]
987 fn test_virtual_list_reset() {
988 let mut list = VirtualList::default();
989 list.set_item_count(100);
990 list.set_viewport_height(300.0);
991 list.set_scroll_position(500.0);
992
993 list.reset();
994 assert_eq!(list.scroll_position(), 0.0);
995 }
996
997 #[test]
998 fn test_visible_range_methods() {
999 let range = VisibleRange {
1000 start: 5,
1001 end: 10,
1002 render_start: 3,
1003 render_end: 12,
1004 offset: 150.0,
1005 };
1006
1007 assert_eq!(range.visible_range(), 5..10);
1008 assert_eq!(range.render_range(), 3..12);
1009 assert_eq!(range.visible_count(), 5);
1010 assert_eq!(range.render_count(), 9);
1011 assert!(range.is_visible(7));
1012 assert!(!range.is_visible(2));
1013 assert!(range.should_render(5));
1014 assert!(!range.should_render(15));
1015 }
1016
1017 #[test]
1018 fn test_item_layout() {
1019 let layout = ItemLayout::new(100.0, 50.0);
1020 assert_eq!(layout.y, 100.0);
1021 assert_eq!(layout.height, 50.0);
1022 assert_eq!(layout.bottom(), 150.0);
1023 }
1024
1025 #[test]
1026 fn test_item_index() {
1027 let index = ItemIndex(42);
1028 assert_eq!(index.as_usize(), 42);
1029
1030 let from_usize: ItemIndex = 100.into();
1031 assert_eq!(from_usize.0, 100);
1032 }
1033
1034 #[test]
1037 fn test_virtual_grid_default() {
1038 let grid = VirtualGrid::default();
1039 assert_eq!(grid.item_count(), 0);
1040 assert_eq!(grid.scroll_position(), 0.0);
1041 }
1042
1043 #[test]
1044 fn test_virtual_grid_set_item_count() {
1045 let mut grid = VirtualGrid::default();
1046 grid.set_item_count(100);
1047 assert_eq!(grid.item_count(), 100);
1048 }
1049
1050 #[test]
1051 fn test_virtual_grid_row_count() {
1052 let config = VirtualGridConfig {
1053 columns: 3,
1054 ..Default::default()
1055 };
1056 let mut grid = VirtualGrid::new(config);
1057 grid.set_item_count(10);
1058 assert_eq!(grid.row_count(), 4); }
1060
1061 #[test]
1062 fn test_virtual_grid_viewport() {
1063 let mut grid = VirtualGrid::default();
1064 grid.set_viewport_height(500.0);
1065 assert_eq!(grid.viewport_height(), 500.0);
1066 }
1067
1068 #[test]
1069 fn test_virtual_grid_scroll_position() {
1070 let mut grid = VirtualGrid::default();
1071 grid.set_item_count(100);
1072 grid.set_viewport_height(500.0);
1073
1074 grid.set_scroll_position(200.0);
1075 assert_eq!(grid.scroll_position(), 200.0);
1076 }
1077
1078 #[test]
1079 fn test_virtual_grid_content_height() {
1080 let config = VirtualGridConfig {
1081 columns: 3,
1082 cell_height: 100.0,
1083 gap: 10.0,
1084 ..Default::default()
1085 };
1086 let mut grid = VirtualGrid::new(config);
1087 grid.set_item_count(9); assert_eq!(grid.content_height(), 320.0);
1091 }
1092
1093 #[test]
1094 fn test_virtual_grid_cell_layout() {
1095 let config = VirtualGridConfig {
1096 columns: 3,
1097 cell_width: 100.0,
1098 cell_height: 80.0,
1099 gap: 10.0,
1100 ..Default::default()
1101 };
1102 let grid = VirtualGrid::new(config);
1103
1104 let layout = grid.get_cell_layout(0);
1106 assert_eq!(layout.x, 0.0);
1107 assert_eq!(layout.y, 0.0);
1108
1109 let layout = grid.get_cell_layout(1);
1111 assert_eq!(layout.x, 110.0);
1112 assert_eq!(layout.y, 0.0);
1113
1114 let layout = grid.get_cell_layout(3);
1116 assert_eq!(layout.x, 0.0);
1117 assert_eq!(layout.y, 90.0);
1118 }
1119
1120 #[test]
1121 fn test_virtual_grid_cell_conversion() {
1122 let config = VirtualGridConfig {
1123 columns: 4,
1124 ..Default::default()
1125 };
1126 let grid = VirtualGrid::new(config);
1127
1128 let cell = grid.index_to_cell(10);
1129 assert_eq!(cell.row, 2);
1130 assert_eq!(cell.col, 2);
1131
1132 assert_eq!(grid.cell_to_index(&cell), 10);
1133 }
1134
1135 #[test]
1136 fn test_virtual_grid_visible_range() {
1137 let config = VirtualGridConfig {
1138 columns: 3,
1139 cell_height: 100.0,
1140 gap: 10.0,
1141 overscan_rows: 1,
1142 ..Default::default()
1143 };
1144 let mut grid = VirtualGrid::new(config);
1145 grid.set_item_count(30); grid.set_viewport_height(250.0);
1147
1148 let range = grid.visible_range().unwrap();
1149 assert_eq!(range.start_row, 0);
1152 assert!(range.end_row >= 2);
1153 }
1154
1155 #[test]
1156 fn test_virtual_grid_scroll_to_item() {
1157 let config = VirtualGridConfig {
1158 columns: 3,
1159 cell_height: 100.0,
1160 gap: 10.0,
1161 ..Default::default()
1162 };
1163 let mut grid = VirtualGrid::new(config);
1164 grid.set_item_count(30);
1165 grid.set_viewport_height(250.0);
1166
1167 grid.scroll_to_item(15, ScrollAlign::Start); assert_eq!(grid.scroll_position(), 550.0); }
1170
1171 #[test]
1172 fn test_virtual_grid_reset() {
1173 let mut grid = VirtualGrid::default();
1174 grid.set_item_count(100);
1175 grid.set_viewport_height(300.0);
1176 grid.set_scroll_position(500.0);
1177
1178 grid.reset();
1179 assert_eq!(grid.scroll_position(), 0.0);
1180 }
1181
1182 #[test]
1183 fn test_grid_cell() {
1184 let cell = GridCell::new(5, 2);
1185 assert_eq!(cell.row, 5);
1186 assert_eq!(cell.col, 2);
1187 }
1188
1189 #[test]
1190 fn test_visible_grid_range_cells() {
1191 let range = VisibleGridRange {
1192 start_row: 2,
1193 end_row: 5,
1194 render_start_row: 1,
1195 render_end_row: 6,
1196 columns: 3,
1197 offset: 100.0,
1198 };
1199
1200 let cells = range.cells_to_render(100);
1202 assert_eq!(cells.len(), 15);
1203
1204 let cells = range.cells_to_render(10);
1207 assert_eq!(cells.len(), 7);
1208 }
1209
1210 #[test]
1211 fn test_visible_grid_range_should_render() {
1212 let range = VisibleGridRange {
1213 start_row: 2,
1214 end_row: 5,
1215 render_start_row: 1,
1216 render_end_row: 6,
1217 columns: 3,
1218 offset: 100.0,
1219 };
1220
1221 assert!(range.should_render_cell(3, 1));
1222 assert!(!range.should_render_cell(0, 0));
1223 assert!(!range.should_render_cell(3, 5)); }
1225
1226 #[test]
1227 fn test_cell_layout() {
1228 let layout = CellLayout::new(100.0, 200.0, 50.0, 60.0);
1229 assert_eq!(layout.x, 100.0);
1230 assert_eq!(layout.y, 200.0);
1231 assert_eq!(layout.width, 50.0);
1232 assert_eq!(layout.height, 60.0);
1233 }
1234
1235 #[test]
1236 fn test_scroll_align_variants() {
1237 assert_ne!(ScrollAlign::Start, ScrollAlign::End);
1239 assert_ne!(ScrollAlign::Center, ScrollAlign::Auto);
1240 }
1241
1242 #[test]
1243 fn test_virtual_list_empty() {
1244 let mut list = VirtualList::default();
1245 list.set_viewport_height(300.0);
1246 assert!(list.visible_range().is_none());
1248 }
1249
1250 #[test]
1251 fn test_virtual_grid_empty() {
1252 let mut grid = VirtualGrid::default();
1253 grid.set_viewport_height(300.0);
1254 assert!(grid.visible_range().is_none());
1256 }
1257
1258 #[test]
1259 fn test_virtual_list_config_default() {
1260 let config = VirtualListConfig::default();
1261 assert_eq!(config.estimated_item_height, 50.0);
1262 assert_eq!(config.overscan_count, 3);
1263 assert!(!config.variable_heights);
1264 assert_eq!(config.initial_scroll, 0.0);
1265 assert_eq!(config.load_more_threshold, 100.0);
1266 }
1267
1268 #[test]
1269 fn test_virtual_grid_config_default() {
1270 let config = VirtualGridConfig::default();
1271 assert_eq!(config.columns, 3);
1272 assert_eq!(config.cell_width, 100.0);
1273 assert_eq!(config.cell_height, 100.0);
1274 assert_eq!(config.gap, 8.0);
1275 assert_eq!(config.overscan_rows, 2);
1276 }
1277}