#![allow(non_snake_case)]
use super::{Attrs, Modifier, Mods, emit_node, push_modifiers, widget_key};
use crate::RangeRequest;
use crate::runtime::__private::{RestartGroup, start_restart_group};
use crate::runtime::composer::{EventCallback, OwnedAttr};
use crate::runtime::{MutableState, group_guard, key, mutable_state_of, remember};
use crate::schema::WidgetKind;
use std::cell::RefCell;
use std::rc::Rc;
struct Interval {
count: usize,
key: Option<Rc<dyn Fn(usize) -> String>>,
content: Rc<dyn Fn(usize)>,
}
#[derive(Default)]
pub struct LazyListScope {
intervals: Vec<Interval>,
}
impl LazyListScope {
pub fn item(&mut self, content: impl Fn() + 'static) {
self.intervals.push(Interval {
count: 1,
key: None,
content: Rc::new(move |_| content()),
});
}
pub fn items(&mut self, count: usize, content: impl Fn(usize) + 'static) {
self.intervals.push(Interval {
count,
key: None,
content: Rc::new(content),
});
}
pub fn items_keyed(
&mut self,
count: usize,
key: impl Fn(usize) -> String + 'static,
content: impl Fn(usize) + 'static,
) {
self.intervals.push(Interval {
count,
key: Some(Rc::new(key)),
content: Rc::new(content),
});
}
fn total(&self) -> usize {
self.intervals.iter().map(|interval| interval.count).sum()
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Kind {
Column,
Row,
Grid,
}
pub struct LazyList {
kind: Kind,
modifier: Modifier,
columns: Option<u32>,
min_column_width: Option<f32>,
done: bool,
}
pub fn LazyColumn() -> LazyList {
LazyList::new(Kind::Column)
}
pub fn LazyRow() -> LazyList {
LazyList::new(Kind::Row)
}
pub fn LazyGrid() -> LazyList {
LazyList::new(Kind::Grid)
}
impl LazyList {
fn new(kind: Kind) -> Self {
Self {
kind,
modifier: Modifier::new(),
columns: None,
min_column_width: None,
done: false,
}
}
pub fn modifier(mut self, modifier: Modifier) -> Self {
self.modifier = modifier;
self
}
pub fn columns(mut self, columns: u32) -> Self {
self.columns = Some(columns);
self
}
pub fn min_column_width(mut self, dp: f32) -> Self {
self.min_column_width = Some(dp);
self
}
pub fn content(mut self, build: impl FnOnce(&mut LazyListScope)) {
self.done = true;
let mut scope = LazyListScope::default();
build(&mut scope);
self.compose(scope);
}
fn compose(&mut self, scope: LazyListScope) {
let (kind, modifier, columns, min_column_width) = (
self.kind,
self.modifier,
self.columns,
self.min_column_width,
);
let (widget, name) = match kind {
Kind::Column => (WidgetKind::LazyColumn, "compose_rust::ui::LazyColumn"),
Kind::Row => (WidgetKind::LazyRow, "compose_rust::ui::LazyRow"),
Kind::Grid => (WidgetKind::LazyGrid, "compose_rust::ui::LazyGrid"),
};
let _group = group_guard(widget_key(name));
let range = remember(|| mutable_state_of((0_usize, 0_usize)));
let total = scope.total();
let request = range.clone();
let listener = EventCallback::Range(Rc::new(RefCell::new(move |asked: RangeRequest| {
request.set((asked.start(), asked.count()));
})));
let intervals = Rc::new(scope.intervals);
emit_node(
widget,
|attrs: &mut Attrs| {
push_modifiers(&modifier, Mods::Standard, attrs);
attrs.push(("item_count", OwnedAttr::Int(total as i64)));
if kind == Kind::Grid {
attrs.push((
"columns",
columns.map_or(OwnedAttr::None, |count| OwnedAttr::Int(i64::from(count))),
));
attrs.push((
"min_column_width",
min_column_width
.map_or(OwnedAttr::None, |dp| OwnedAttr::Float(f64::from(dp))),
));
}
},
vec![("onrangerequest", listener)],
|| Items(range, intervals),
);
}
}
impl Drop for LazyList {
fn drop(&mut self) {
if !self.done && !std::thread::panicking() {
self.done = true;
self.compose(LazyListScope::default());
}
}
}
const ITEMS: u64 = widget_key("compose_rust::ui::LazyList::items");
const ITEM: u64 = widget_key("compose_rust::ui::LazyList::item");
const ITEM_BOX: u64 = widget_key("compose_rust::ui::LazyList::item_box");
#[derive(Clone)]
struct SameIntervals(Rc<Vec<Interval>>);
impl PartialEq for SameIntervals {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.0, &other.0)
}
}
fn Items(range: MutableState<(usize, usize)>, intervals: Rc<Vec<Interval>>) {
let scope: RestartGroup = start_restart_group(ITEMS);
let fresh = scope.param_changed(0, &SameIntervals(Rc::clone(&intervals)));
if !scope.must_run() && !fresh {
scope.skip();
return;
}
{
let (range, intervals) = (range.clone(), Rc::clone(&intervals));
scope.set_restart(move || Items(range.clone(), Rc::clone(&intervals)));
}
let total: usize = intervals.iter().map(|interval| interval.count).sum();
let (start, count) = range.get();
let first = start.min(total);
let last = first.saturating_add(count).min(total);
for index in first..last {
let mut offset = index;
let Some(interval) = intervals.iter().find(|interval| {
if offset < interval.count {
true
} else {
offset -= interval.count;
false
}
}) else {
continue;
};
let item_key = interval
.key
.as_ref()
.map_or_else(|| index.to_string(), |key| key(offset));
let content = Rc::clone(&interval.content);
let local = offset;
key(item_key.clone(), || {
let _group = group_guard(ITEM_BOX);
emit_node(
WidgetKind::Box,
|attrs: &mut Attrs| attrs.push(("item_key", OwnedAttr::Text(item_key))),
Vec::new(),
|| Item(content, local),
);
});
}
}
#[derive(Clone)]
struct SameItem(Rc<dyn Fn(usize)>, usize);
impl PartialEq for SameItem {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.0, &other.0) && self.1 == other.1
}
}
fn Item(content: Rc<dyn Fn(usize)>, index: usize) {
let scope: RestartGroup = start_restart_group(ITEM);
let changed = scope.param_changed(0, &SameItem(Rc::clone(&content), index));
if !scope.must_run() && !changed {
scope.skip();
return;
}
{
let content = Rc::clone(&content);
scope.set_restart(move || Item(Rc::clone(&content), index));
}
content(index);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fr39_the_items_of_several_runs_are_one_index_space() {
let mut scope = LazyListScope::default();
scope.item(|| {});
scope.items(3, |_| {});
scope.items_keyed(2, |index| format!("k{index}"), |_| {});
assert_eq!(scope.total(), 6);
}
}