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