1#![allow(non_snake_case)]
22
23use super::{Attrs, Modifier, Mods, emit_node, push_modifiers, widget_key};
24use crate::RangeRequest;
25use crate::runtime::__private::{RestartGroup, start_restart_group};
26use crate::runtime::composer::{EventCallback, OwnedAttr};
27use crate::runtime::{MutableState, group_guard, key, mutable_state_of, remember};
28use crate::schema::WidgetKind;
29use std::cell::RefCell;
30use std::rc::Rc;
31
32struct Interval {
34 count: usize,
35 key: Option<Rc<dyn Fn(usize) -> String>>,
36 content: Rc<dyn Fn(usize)>,
37}
38
39#[derive(Default)]
41pub struct LazyListScope {
42 intervals: Vec<Interval>,
43}
44
45impl LazyListScope {
46 pub fn item(&mut self, content: impl Fn() + 'static) {
48 self.intervals.push(Interval {
49 count: 1,
50 key: None,
51 content: Rc::new(move |_| content()),
52 });
53 }
54
55 pub fn items(&mut self, count: usize, content: impl Fn(usize) + 'static) {
59 self.intervals.push(Interval {
60 count,
61 key: None,
62 content: Rc::new(content),
63 });
64 }
65
66 pub fn items_keyed(
69 &mut self,
70 count: usize,
71 key: impl Fn(usize) -> String + 'static,
72 content: impl Fn(usize) + 'static,
73 ) {
74 self.intervals.push(Interval {
75 count,
76 key: Some(Rc::new(key)),
77 content: Rc::new(content),
78 });
79 }
80
81 fn total(&self) -> usize {
82 self.intervals.iter().map(|interval| interval.count).sum()
83 }
84}
85
86#[derive(Clone, Copy, PartialEq, Eq)]
88enum Kind {
89 Column,
90 Row,
91 Grid,
92}
93
94pub struct LazyList {
96 kind: Kind,
97 modifier: Modifier,
98 columns: Option<u32>,
99 min_column_width: Option<f32>,
100 done: bool,
101}
102
103pub fn LazyColumn() -> LazyList {
105 LazyList::new(Kind::Column)
106}
107
108pub fn LazyRow() -> LazyList {
110 LazyList::new(Kind::Row)
111}
112
113pub fn LazyGrid() -> LazyList {
116 LazyList::new(Kind::Grid)
117}
118
119impl LazyList {
120 fn new(kind: Kind) -> Self {
121 Self {
122 kind,
123 modifier: Modifier::new(),
124 columns: None,
125 min_column_width: None,
126 done: false,
127 }
128 }
129
130 pub fn modifier(mut self, modifier: Modifier) -> Self {
131 self.modifier = modifier;
132 self
133 }
134
135 pub fn columns(mut self, columns: u32) -> Self {
137 self.columns = Some(columns);
138 self
139 }
140
141 pub fn min_column_width(mut self, dp: f32) -> Self {
143 self.min_column_width = Some(dp);
144 self
145 }
146
147 pub fn content(mut self, build: impl FnOnce(&mut LazyListScope)) {
149 self.done = true;
150 let mut scope = LazyListScope::default();
151 build(&mut scope);
152 self.compose(scope);
153 }
154
155 fn compose(&mut self, scope: LazyListScope) {
156 let (kind, modifier, columns, min_column_width) = (
157 self.kind,
158 self.modifier,
159 self.columns,
160 self.min_column_width,
161 );
162 let (widget, name) = match kind {
163 Kind::Column => (WidgetKind::LazyColumn, "compose_rust::ui::LazyColumn"),
164 Kind::Row => (WidgetKind::LazyRow, "compose_rust::ui::LazyRow"),
165 Kind::Grid => (WidgetKind::LazyGrid, "compose_rust::ui::LazyGrid"),
166 };
167 let _group = group_guard(widget_key(name));
168 let range = remember(|| mutable_state_of((0_usize, 0_usize)));
171 let total = scope.total();
172 let request = range.clone();
173 let listener = EventCallback::Range(Rc::new(RefCell::new(move |asked: RangeRequest| {
174 request.set((asked.start(), asked.count()));
175 })));
176 let intervals = Rc::new(scope.intervals);
177 emit_node(
178 widget,
179 |attrs: &mut Attrs| {
180 push_modifiers(&modifier, Mods::Standard, attrs);
181 attrs.push(("item_count", OwnedAttr::Int(total as i64)));
182 if kind == Kind::Grid {
183 attrs.push((
184 "columns",
185 columns.map_or(OwnedAttr::None, |count| OwnedAttr::Int(i64::from(count))),
186 ));
187 attrs.push((
188 "min_column_width",
189 min_column_width
190 .map_or(OwnedAttr::None, |dp| OwnedAttr::Float(f64::from(dp))),
191 ));
192 }
193 },
194 vec![("onrangerequest", listener)],
195 || Items(range, intervals),
196 );
197 }
198}
199
200impl Drop for LazyList {
201 fn drop(&mut self) {
202 if !self.done && !std::thread::panicking() {
203 self.done = true;
204 self.compose(LazyListScope::default());
205 }
206 }
207}
208
209const ITEMS: u64 = widget_key("compose_rust::ui::LazyList::items");
210const ITEM: u64 = widget_key("compose_rust::ui::LazyList::item");
211const ITEM_BOX: u64 = widget_key("compose_rust::ui::LazyList::item_box");
212
213#[derive(Clone)]
216struct SameIntervals(Rc<Vec<Interval>>);
217
218impl PartialEq for SameIntervals {
219 fn eq(&self, other: &Self) -> bool {
220 Rc::ptr_eq(&self.0, &other.0)
221 }
222}
223
224fn Items(range: MutableState<(usize, usize)>, intervals: Rc<Vec<Interval>>) {
226 let scope: RestartGroup = start_restart_group(ITEMS);
227 let fresh = scope.param_changed(0, &SameIntervals(Rc::clone(&intervals)));
228 if !scope.must_run() && !fresh {
229 scope.skip();
230 return;
231 }
232 {
233 let (range, intervals) = (range.clone(), Rc::clone(&intervals));
234 scope.set_restart(move || Items(range.clone(), Rc::clone(&intervals)));
235 }
236 let total: usize = intervals.iter().map(|interval| interval.count).sum();
237 let (start, count) = range.get();
238 let first = start.min(total);
239 let last = first.saturating_add(count).min(total);
240 for index in first..last {
241 let mut offset = index;
242 let Some(interval) = intervals.iter().find(|interval| {
243 if offset < interval.count {
244 true
245 } else {
246 offset -= interval.count;
247 false
248 }
249 }) else {
250 continue;
251 };
252 let item_key = interval
253 .key
254 .as_ref()
255 .map_or_else(|| index.to_string(), |key| key(offset));
256 let content = Rc::clone(&interval.content);
257 let local = offset;
258 key(item_key.clone(), || {
259 let _group = group_guard(ITEM_BOX);
260 emit_node(
261 WidgetKind::Box,
262 |attrs: &mut Attrs| attrs.push(("item_key", OwnedAttr::Text(item_key))),
263 Vec::new(),
264 || Item(content, local),
265 );
266 });
267 }
268}
269
270#[derive(Clone)]
272struct SameItem(Rc<dyn Fn(usize)>, usize);
273
274impl PartialEq for SameItem {
275 fn eq(&self, other: &Self) -> bool {
276 Rc::ptr_eq(&self.0, &other.0) && self.1 == other.1
277 }
278}
279
280fn Item(content: Rc<dyn Fn(usize)>, index: usize) {
283 let scope: RestartGroup = start_restart_group(ITEM);
284 let changed = scope.param_changed(0, &SameItem(Rc::clone(&content), index));
285 if !scope.must_run() && !changed {
286 scope.skip();
287 return;
288 }
289 {
290 let content = Rc::clone(&content);
291 scope.set_restart(move || Item(Rc::clone(&content), index));
292 }
293 content(index);
294}
295
296#[cfg(test)]
297mod tests {
298 use super::*;
299
300 #[test]
301 fn fr39_the_items_of_several_runs_are_one_index_space() {
302 let mut scope = LazyListScope::default();
303 scope.item(|| {});
304 scope.items(3, |_| {});
305 scope.items_keyed(2, |index| format!("k{index}"), |_| {});
306 assert_eq!(scope.total(), 6);
307 }
308}