use std::rc::Rc;
use smudge_core::element::{Element, IntoElement, for_each};
use smudge_core::reactive::{RwSignal, prelude::*};
use smudge_core::style::{Edges, Length, px};
use smudge_core::widget::panel;
use crate::scroll::{ScrollExt, ScrollView, scroll_with_visible_height};
const OVERSCAN: usize = 4;
pub fn virtual_list<E: IntoElement>(
count: impl Fn() -> usize + 'static,
row_height: f32,
row: impl Fn(usize) -> E + 'static,
) -> Element<ScrollView> {
let row_height = if row_height.is_finite() && row_height > 0.0 {
row_height
} else {
0.0
};
let count = Rc::new(count);
let row = Rc::new(row);
let visible: RwSignal<Option<f32>> = RwSignal::new(None);
let offset = RwSignal::new(0.0);
let range_count = Rc::clone(&count);
let range_offset = offset.clone();
let range_visible = visible.clone();
let rows = for_each(
move || {
let count = range_count();
let Some(viewport) = range_visible.get() else {
return Vec::new();
};
if row_height == 0.0 {
return Vec::new();
}
let offset = range_offset.get();
let first = (offset / row_height).floor();
let last = ((offset + viewport) / row_height).ceil();
let start = (first as usize).saturating_sub(OVERSCAN);
let end = (last as usize).saturating_add(OVERSCAN).min(count);
(start..end).collect()
},
|&index| index,
move |index| {
panel()
.absolute()
.inset(Edges {
top: px(index as f32 * row_height),
right: px(0.0),
bottom: Length::Auto,
left: px(0.0),
})
.child(row(index))
},
);
let height_count = Rc::clone(&count);
let content = panel()
.column()
.height(move || px(height_count() as f32 * row_height))
.child(rows);
scroll_with_visible_height(content, visible).offset(offset)
}