1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#[cfg(feature = "theme")]
use crate::theme::use_theme;
use dioxus::prelude::*;
/// Renderer virtualizzato ad alte prestazioni (60 FPS) per elenchi massivi di elementi.
/// Esegue il rendering nel DOM unicamente degli elementi visibili nella finestra di scorrimento corrente.
#[component]
pub fn VirtualList(
/// Numero complessivo totale di elementi nell'elenco.
item_count: usize,
/// Altezza fissa in pixel di ogni singola riga/elemento. Defaults to 48.
#[props(default = 48)]
item_height: u32,
/// Altezza complessiva in pixel del viewport visibile. Defaults to 360.
#[props(default = 360)]
height: u32,
/// Numero di elementi aggiuntivi da pre-renderizzare sopra e sotto la finestra (overscan). Defaults to 4.
#[props(default = 4)]
overscan: usize,
/// Funzione di rendering per il singolo elemento dato il suo indice numerico (0..item_count).
render_item: Callback<usize, Element>,
/// Classi CSS aggiuntive per il contenitore.
#[props(default)]
class: String,
) -> Element {
let mut scroll_top = use_signal(|| 0u32);
let total_height = (item_count as u64) * (item_height as u64);
let cur_scroll = *scroll_top.read();
let safe_item_height = item_height.max(1);
let start_idx = (cur_scroll / safe_item_height) as usize;
let visible_count = height.div_ceil(safe_item_height) as usize;
let render_start = start_idx.saturating_sub(overscan);
let render_end = (start_idx + visible_count + overscan).min(item_count);
let offset_y = (render_start as u32) * item_height;
#[cfg(feature = "theme")]
let _theme = use_theme();
rsx! {
div {
class: "sd-virtual-list {class}",
style: "height: {height}px; overflow-y: auto; position: relative;",
onscroll: move |e| {
let top = (e.data().scroll_top().max(0.0)) as u32;
scroll_top.set(top);
},
// Elemento fantasma che simula l'intera altezza dello scorrimento
div {
class: "sd-virtual-list-spacer",
style: "height: {total_height}px; width: 100%; pointer-events: none;",
}
// Slice renderizzata traslata alla posizione corrente
div {
class: "sd-virtual-list-content",
style: "transform: translateY({offset_y}px); position: absolute; top: 0; left: 0; right: 0;",
for idx in render_start..render_end {
div {
key: "{idx}",
class: "sd-virtual-list-item",
style: "height: {item_height}px;",
{render_item.call(idx)}
}
}
}
}
}
}