1use crate::{
2 geometry::{
3 rect::RectF,
4 vector::{vec2f, Vector2F},
5 },
6 json::json,
7 sum_tree::{self, Bias, SumTree},
8 DebugContext, Element, ElementBox, ElementRc, Event, EventContext, LayoutContext, PaintContext,
9 SizeConstraint,
10};
11use std::{cell::RefCell, ops::Range, rc::Rc};
12
13pub struct List {
14 state: ListState,
15}
16
17#[derive(Clone)]
18pub struct ListState(Rc<RefCell<StateInner>>);
19
20#[derive(Clone, Copy, Debug, Eq, PartialEq)]
21pub enum Orientation {
22 Top,
23 Bottom,
24}
25
26struct StateInner {
27 last_layout_width: Option<f32>,
28 render_item: Box<dyn FnMut(usize, &mut LayoutContext) -> ElementBox>,
29 rendered_range: Range<usize>,
30 items: SumTree<ListItem>,
31 scroll_top: Option<ScrollTop>,
32 orientation: Orientation,
33 overdraw: f32,
34 scroll_handler: Option<Box<dyn FnMut(Range<usize>, &mut EventContext)>>,
35}
36
37#[derive(Clone, Copy, Debug, Default, PartialEq)]
38pub struct ScrollTop {
39 item_ix: usize,
40 offset_in_item: f32,
41}
42
43#[derive(Clone)]
44enum ListItem {
45 Unrendered,
46 Rendered(ElementRc),
47 Removed(f32),
48}
49
50impl std::fmt::Debug for ListItem {
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 match self {
53 Self::Unrendered => write!(f, "Unrendered"),
54 Self::Rendered(_) => f.debug_tuple("Rendered").finish(),
55 Self::Removed(height) => f.debug_tuple("Removed").field(height).finish(),
56 }
57 }
58}
59
60#[derive(Clone, Debug, Default, PartialEq)]
61struct ListItemSummary {
62 count: usize,
63 rendered_count: usize,
64 unrendered_count: usize,
65 height: f32,
66}
67
68#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
69struct Count(usize);
70
71#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
72struct RenderedCount(usize);
73
74#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
75struct UnrenderedCount(usize);
76
77#[derive(Clone, Debug, Default)]
78struct Height(f32);
79
80impl List {
81 pub fn new(state: ListState) -> Self {
82 Self { state }
83 }
84}
85
86impl Element for List {
87 type LayoutState = ScrollTop;
88 type PaintState = ();
89
90 fn layout(
91 &mut self,
92 constraint: SizeConstraint,
93 cx: &mut LayoutContext,
94 ) -> (Vector2F, Self::LayoutState) {
95 let state = &mut *self.state.0.borrow_mut();
96 let size = constraint.max;
97 let mut item_constraint = constraint;
98 item_constraint.min.set_y(0.);
99 item_constraint.max.set_y(f32::INFINITY);
100
101 if state.last_layout_width != Some(size.x()) {
102 state.rendered_range = 0..0;
103 state.items = SumTree::from_iter(
104 (0..state.items.summary().count).map(|_| ListItem::Unrendered),
105 &(),
106 )
107 }
108
109 let overdraw = state.overdraw;
110 let old_rendered_range = state.rendered_range.clone();
111 let old_items = state.items.clone();
112 let orientation = state.orientation;
113 let stored_scroll_top = state.scroll_top;
114 let mut new_items = SumTree::new();
115
116 let mut render_item = |ix, old_item: &ListItem| {
117 let element = if let ListItem::Rendered(element) = old_item {
118 element.clone()
119 } else {
120 let mut element = (state.render_item)(ix, cx);
121 element.layout(item_constraint, cx);
122 element.into()
123 };
124 element
125 };
126
127 // Determine the items that need to be rendered in order to cover the visible area plus the
128 // overdraw. When parked at the end of a bottom-oriented list, this requires rendering items
129 // starting from the end of the list until the visible region is full. In other cases, the
130 // stored scroll can be used.
131 let mut new_rendered_start_ix;
132 let mut rendered_items = Vec::new();
133 let scroll_top;
134 if let (Orientation::Bottom, None) = (orientation, stored_scroll_top) {
135 let mut rendered_height = 0.;
136 let mut cursor = old_items.cursor::<Count, ()>();
137 cursor.seek(&Count(old_items.summary().count), Bias::Left, &());
138
139 while let Some(item) = cursor.item() {
140 let element = render_item(cursor.seek_start().0, item);
141 rendered_height += element.size().y();
142 rendered_items.push(ListItem::Rendered(element));
143 if rendered_height >= size.y() {
144 break;
145 } else {
146 cursor.prev(&());
147 }
148 }
149 scroll_top = ScrollTop {
150 item_ix: cursor.seek_start().0,
151 offset_in_item: rendered_height - size.y(),
152 };
153
154 cursor.prev(&());
155 while let Some(item) = cursor.item() {
156 let element = render_item(cursor.seek_start().0, item);
157 rendered_height += element.size().y();
158 rendered_items.push(ListItem::Rendered(element));
159 if rendered_height >= size.y() + overdraw {
160 break;
161 } else {
162 cursor.prev(&());
163 }
164 }
165
166 rendered_items.reverse();
167 new_rendered_start_ix = cursor.seek_start().0;
168 } else {
169 scroll_top = stored_scroll_top.unwrap_or_default();
170 new_rendered_start_ix = scroll_top.item_ix;
171 let mut cursor = old_items.cursor::<Count, ()>();
172 cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
173 cursor.prev(&());
174 let mut rendered_height = scroll_top.offset_in_item;
175 while let Some(item) = cursor.item() {
176 if rendered_height >= overdraw {
177 break;
178 }
179
180 let element = render_item(cursor.seek_start().0, item);
181 rendered_height += element.size().y();
182 rendered_items.push(ListItem::Rendered(element));
183 new_rendered_start_ix -= 1;
184 cursor.prev(&());
185 }
186 rendered_items.reverse();
187
188 let mut rendered_height = 0.;
189 cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
190 for (ix, item) in cursor.enumerate() {
191 if rendered_height >= size.y() + overdraw {
192 break;
193 }
194
195 let element = render_item(scroll_top.item_ix + ix, item);
196 rendered_height += element.size().y();
197 if ix == 0 {
198 rendered_height -= scroll_top.offset_in_item;
199 }
200 rendered_items.push(ListItem::Rendered(element));
201 }
202 }
203 let new_rendered_range =
204 new_rendered_start_ix..new_rendered_start_ix + rendered_items.len();
205
206 let mut cursor = old_items.cursor::<Count, ()>();
207
208 // Discard any rendered elements before the new rendered range.
209 if old_rendered_range.start < new_rendered_range.start {
210 new_items.push_tree(
211 cursor.slice(&Count(old_rendered_range.start), Bias::Right, &()),
212 &(),
213 );
214 let remove_to = old_rendered_range.end.min(new_rendered_range.start);
215 while cursor.seek_start().0 < remove_to {
216 new_items.push(cursor.item().unwrap().remove(), &());
217 cursor.next(&());
218 }
219 }
220
221 new_items.push_tree(
222 cursor.slice(&Count(new_rendered_range.start), Bias::Right, &()),
223 &(),
224 );
225 new_items.extend(rendered_items, &());
226 cursor.seek(&Count(new_rendered_range.end), Bias::Right, &());
227
228 if new_rendered_range.end < old_rendered_range.start {
229 new_items.push_tree(
230 cursor.slice(&Count(old_rendered_range.start), Bias::Right, &()),
231 &(),
232 );
233 }
234
235 while cursor.seek_start().0 < old_rendered_range.end {
236 new_items.push(cursor.item().unwrap().remove(), &());
237 cursor.next(&());
238 }
239
240 new_items.push_tree(cursor.suffix(&()), &());
241
242 drop(cursor);
243 state.items = new_items;
244 state.rendered_range = new_rendered_range;
245 state.last_layout_width = Some(size.x());
246 (size, scroll_top)
247 }
248
249 fn paint(&mut self, bounds: RectF, scroll_top: &mut ScrollTop, cx: &mut PaintContext) {
250 cx.scene.push_layer(Some(bounds));
251
252 let state = &mut *self.state.0.borrow_mut();
253 for (mut element, origin) in state.visible_elements(bounds, scroll_top) {
254 element.paint(origin, cx);
255 }
256
257 cx.scene.pop_layer();
258 }
259
260 fn dispatch_event(
261 &mut self,
262 event: &Event,
263 bounds: RectF,
264 scroll_top: &mut ScrollTop,
265 _: &mut (),
266 cx: &mut EventContext,
267 ) -> bool {
268 let mut handled = false;
269
270 let mut state = self.state.0.borrow_mut();
271 for (mut element, _) in state.visible_elements(bounds, scroll_top) {
272 handled = element.dispatch_event(event, cx) || handled;
273 }
274
275 match event {
276 Event::ScrollWheel {
277 position,
278 delta,
279 precise,
280 } => {
281 if bounds.contains_point(*position) {
282 if state.scroll(scroll_top, bounds.height(), *delta, *precise, cx) {
283 handled = true;
284 }
285 }
286 }
287 _ => {}
288 }
289
290 handled
291 }
292
293 fn debug(
294 &self,
295 bounds: RectF,
296 scroll_top: &Self::LayoutState,
297 _: &(),
298 cx: &DebugContext,
299 ) -> serde_json::Value {
300 let state = self.state.0.borrow_mut();
301 let visible_elements = state
302 .visible_elements(bounds, scroll_top)
303 .map(|e| e.0.debug(cx))
304 .collect::<Vec<_>>();
305 let visible_range = scroll_top.item_ix..(scroll_top.item_ix + visible_elements.len());
306 json!({
307 "visible_range": visible_range,
308 "visible_elements": visible_elements,
309 "scroll_top": state.scroll_top.map(|top| (top.item_ix, top.offset_in_item)),
310 })
311 }
312}
313
314impl ListState {
315 pub fn new<F>(
316 element_count: usize,
317 orientation: Orientation,
318 overdraw: f32,
319 render_item: F,
320 ) -> Self
321 where
322 F: 'static + FnMut(usize, &mut LayoutContext) -> ElementBox,
323 {
324 let mut items = SumTree::new();
325 items.extend((0..element_count).map(|_| ListItem::Unrendered), &());
326 Self(Rc::new(RefCell::new(StateInner {
327 last_layout_width: None,
328 render_item: Box::new(render_item),
329 rendered_range: 0..0,
330 items,
331 scroll_top: None,
332 orientation,
333 overdraw,
334 scroll_handler: None,
335 })))
336 }
337
338 pub fn reset(&self, element_count: usize) {
339 let state = &mut *self.0.borrow_mut();
340 state.scroll_top = None;
341 state.items = SumTree::new();
342 state
343 .items
344 .extend((0..element_count).map(|_| ListItem::Unrendered), &());
345 }
346
347 pub fn splice(&self, old_range: Range<usize>, count: usize) {
348 let state = &mut *self.0.borrow_mut();
349
350 if let Some(ScrollTop {
351 item_ix,
352 offset_in_item,
353 }) = state.scroll_top.as_mut()
354 {
355 if old_range.contains(item_ix) {
356 *item_ix = old_range.start;
357 *offset_in_item = 0.;
358 } else if old_range.end <= *item_ix {
359 *item_ix = *item_ix - (old_range.end - old_range.start) + count;
360 }
361 }
362
363 let new_end = old_range.start + count;
364 if old_range.start < state.rendered_range.start {
365 state.rendered_range.start =
366 new_end + state.rendered_range.start.saturating_sub(old_range.end);
367 }
368 if old_range.start < state.rendered_range.end {
369 state.rendered_range.end =
370 new_end + state.rendered_range.end.saturating_sub(old_range.end);
371 }
372
373 let mut old_heights = state.items.cursor::<Count, ()>();
374 let mut new_heights = old_heights.slice(&Count(old_range.start), Bias::Right, &());
375 old_heights.seek_forward(&Count(old_range.end), Bias::Right, &());
376
377 new_heights.extend((0..count).map(|_| ListItem::Unrendered), &());
378 new_heights.push_tree(old_heights.suffix(&()), &());
379 drop(old_heights);
380 state.items = new_heights;
381 }
382
383 pub fn set_scroll_handler(
384 &mut self,
385 handler: impl FnMut(Range<usize>, &mut EventContext) + 'static,
386 ) {
387 self.0.borrow_mut().scroll_handler = Some(Box::new(handler))
388 }
389}
390
391impl StateInner {
392 fn visible_range(&self, height: f32, scroll_top: &ScrollTop) -> Range<usize> {
393 let mut cursor = self.items.cursor::<Count, Height>();
394 cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
395 let start_y = cursor.sum_start().0 + scroll_top.offset_in_item;
396 let mut cursor = cursor.swap_dimensions();
397 cursor.seek_forward(&Height(start_y + height), Bias::Left, &());
398 scroll_top.item_ix..cursor.sum_start().0 + 1
399 }
400
401 fn visible_elements<'a>(
402 &'a self,
403 bounds: RectF,
404 scroll_top: &ScrollTop,
405 ) -> impl Iterator<Item = (ElementRc, Vector2F)> + 'a {
406 let mut item_origin = bounds.origin() - vec2f(0., scroll_top.offset_in_item);
407 let mut cursor = self.items.cursor::<Count, ()>();
408 cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
409 std::iter::from_fn(move || {
410 while let Some(item) = cursor.item() {
411 if item_origin.y() > bounds.max_y() {
412 break;
413 }
414
415 if let ListItem::Rendered(element) = item {
416 let result = (element.clone(), item_origin);
417 item_origin.set_y(item_origin.y() + element.size().y());
418 cursor.next(&());
419 return Some(result);
420 }
421
422 cursor.next(&());
423 }
424
425 None
426 })
427 }
428
429 fn scroll(
430 &mut self,
431 scroll_top: &ScrollTop,
432 height: f32,
433 mut delta: Vector2F,
434 precise: bool,
435 cx: &mut EventContext,
436 ) -> bool {
437 if !precise {
438 delta *= 20.;
439 }
440
441 let scroll_max = (self.items.summary().height - height).max(0.);
442 let new_scroll_top = (self.scroll_top(height) + delta.y())
443 .max(0.)
444 .min(scroll_max);
445
446 if self.orientation == Orientation::Bottom && new_scroll_top == scroll_max {
447 self.scroll_top = None;
448 } else {
449 let mut cursor = self.items.cursor::<Height, Count>();
450 cursor.seek(&Height(new_scroll_top), Bias::Right, &());
451 let item_ix = cursor.sum_start().0;
452 let offset_in_item = new_scroll_top - cursor.seek_start().0;
453 self.scroll_top = Some(ScrollTop {
454 item_ix,
455 offset_in_item,
456 });
457 }
458
459 if self.scroll_handler.is_some() {
460 let visible_range = self.visible_range(height, scroll_top);
461 self.scroll_handler.as_mut().unwrap()(visible_range, cx);
462 }
463 cx.notify();
464
465 true
466 }
467
468 fn scroll_top(&self, height: f32) -> f32 {
469 let scroll_max = (self.items.summary().height - height).max(0.);
470 if let Some(ScrollTop {
471 item_ix,
472 offset_in_item,
473 }) = self.scroll_top
474 {
475 let mut cursor = self.items.cursor::<Count, Height>();
476 cursor.seek(&Count(item_ix), Bias::Right, &());
477 (cursor.sum_start().0 + offset_in_item).min(scroll_max)
478 } else {
479 match self.orientation {
480 Orientation::Top => 0.,
481 Orientation::Bottom => scroll_max,
482 }
483 }
484 }
485}
486
487impl ListItem {
488 fn remove(&self) -> Self {
489 match self {
490 ListItem::Unrendered => ListItem::Unrendered,
491 ListItem::Rendered(element) => ListItem::Removed(element.size().y()),
492 ListItem::Removed(height) => ListItem::Removed(*height),
493 }
494 }
495}
496
497impl sum_tree::Item for ListItem {
498 type Summary = ListItemSummary;
499
500 fn summary(&self) -> Self::Summary {
501 match self {
502 ListItem::Unrendered => ListItemSummary {
503 count: 1,
504 rendered_count: 0,
505 unrendered_count: 1,
506 height: 0.,
507 },
508 ListItem::Rendered(element) => ListItemSummary {
509 count: 1,
510 rendered_count: 1,
511 unrendered_count: 0,
512 height: element.size().y(),
513 },
514 ListItem::Removed(height) => ListItemSummary {
515 count: 1,
516 rendered_count: 0,
517 unrendered_count: 1,
518 height: *height,
519 },
520 }
521 }
522}
523
524impl sum_tree::Summary for ListItemSummary {
525 type Context = ();
526
527 fn add_summary(&mut self, summary: &Self, _: &()) {
528 self.count += summary.count;
529 self.rendered_count += summary.rendered_count;
530 self.unrendered_count += summary.unrendered_count;
531 self.height += summary.height;
532 }
533}
534
535impl<'a> sum_tree::Dimension<'a, ListItemSummary> for ListItemSummary {
536 fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
537 sum_tree::Summary::add_summary(self, summary, &());
538 }
539}
540
541impl<'a> sum_tree::Dimension<'a, ListItemSummary> for Count {
542 fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
543 self.0 += summary.count;
544 }
545}
546
547impl<'a> sum_tree::Dimension<'a, ListItemSummary> for RenderedCount {
548 fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
549 self.0 += summary.rendered_count;
550 }
551}
552
553impl<'a> sum_tree::Dimension<'a, ListItemSummary> for UnrenderedCount {
554 fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
555 self.0 += summary.unrendered_count;
556 }
557}
558
559impl<'a> sum_tree::Dimension<'a, ListItemSummary> for Height {
560 fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
561 self.0 += summary.height;
562 }
563}
564
565impl<'a> sum_tree::SeekDimension<'a, ListItemSummary> for Height {
566 fn cmp(&self, other: &Self, _: &()) -> std::cmp::Ordering {
567 self.0.partial_cmp(&other.0).unwrap()
568 }
569}
570
571#[cfg(test)]
572mod tests {
573 use super::*;
574 use crate::geometry::vector::vec2f;
575 use rand::prelude::*;
576 use std::env;
577
578 #[crate::test(self)]
579 fn test_layout(cx: &mut crate::MutableAppContext) {
580 let mut presenter = cx.build_presenter(0, 0.);
581
582 let elements = Rc::new(RefCell::new(vec![(0, 20.), (1, 30.), (2, 100.)]));
583 let state = ListState::new(elements.borrow().len(), Orientation::Top, 1000.0, {
584 let elements = elements.clone();
585 move |ix, _| {
586 let (id, height) = elements.borrow()[ix];
587 TestElement::new(id, height).boxed()
588 }
589 });
590
591 let mut list = List::new(state.clone()).boxed();
592 let size = list.layout(
593 SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.)),
594 &mut presenter.build_layout_context(cx),
595 );
596 assert_eq!(size, vec2f(100., 40.));
597 assert_eq!(
598 state.0.borrow().items.summary(),
599 ListItemSummary {
600 count: 3,
601 rendered_count: 3,
602 unrendered_count: 0,
603 height: 150.
604 }
605 );
606
607 state.0.borrow_mut().scroll(
608 &ScrollTop {
609 item_ix: 0,
610 offset_in_item: 0.,
611 },
612 40.,
613 vec2f(0., 54.),
614 true,
615 &mut presenter.build_event_context(cx),
616 );
617 assert_eq!(
618 state.0.borrow().scroll_top,
619 Some(ScrollTop {
620 item_ix: 2,
621 offset_in_item: 4.
622 })
623 );
624 assert_eq!(state.0.borrow().scroll_top(size.y()), 54.);
625
626 elements.borrow_mut().splice(1..2, vec![(3, 40.), (4, 50.)]);
627 elements.borrow_mut().push((5, 60.));
628 state.splice(1..2, 2);
629 state.splice(4..4, 1);
630 assert_eq!(
631 state.0.borrow().items.summary(),
632 ListItemSummary {
633 count: 5,
634 rendered_count: 2,
635 unrendered_count: 3,
636 height: 120.
637 }
638 );
639
640 let mut list = List::new(state.clone()).boxed();
641 let size = list.layout(
642 SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.)),
643 &mut presenter.build_layout_context(cx),
644 );
645 assert_eq!(size, vec2f(100., 40.));
646 assert_eq!(
647 state.0.borrow().items.summary(),
648 ListItemSummary {
649 count: 5,
650 rendered_count: 5,
651 unrendered_count: 0,
652 height: 270.
653 }
654 );
655 assert_eq!(
656 state.0.borrow().scroll_top,
657 Some(ScrollTop {
658 item_ix: 3,
659 offset_in_item: 4.
660 })
661 );
662 assert_eq!(state.0.borrow().scroll_top(size.y()), 114.);
663 }
664
665 #[crate::test(self, iterations = 10000, seed = 119)]
666 fn test_random(cx: &mut crate::MutableAppContext, mut rng: StdRng) {
667 let operations = env::var("OPERATIONS")
668 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
669 .unwrap_or(10);
670 let operations = 3;
671
672 let mut presenter = cx.build_presenter(0, 0.);
673 let mut next_id = 0;
674 let elements = Rc::new(RefCell::new(
675 (0..rng.gen_range(0..=20))
676 .map(|_| {
677 let id = next_id;
678 next_id += 1;
679 (id, rng.gen_range(0_f32..=100_f32))
680 })
681 .collect::<Vec<_>>(),
682 ));
683 let orientation = *[Orientation::Top, Orientation::Bottom]
684 .choose(&mut rng)
685 .unwrap();
686 let overdraw = rng.gen_range(1_f32..=100_f32);
687 let state = ListState::new(elements.borrow().len(), orientation, overdraw, {
688 let elements = elements.clone();
689 move |ix, _| {
690 let (id, height) = elements.borrow()[ix];
691 TestElement::new(id, height).boxed()
692 }
693 });
694
695 let mut width = rng.gen_range(0_f32..=1000_f32);
696 let mut height = rng.gen_range(0_f32..=1000_f32);
697 log::info!("orientation: {:?}", orientation);
698 log::info!("overdraw: {}", overdraw);
699 log::info!("elements: {:?}", elements.borrow());
700 log::info!("size: ({:?}, {:?})", width, height);
701 log::info!("==================");
702
703 let mut scroll_top = None;
704 for _ in 0..operations {
705 match rng.gen_range(0..=100) {
706 0..=29 if scroll_top.is_some() => {
707 let delta = vec2f(0., rng.gen_range(-overdraw..=overdraw));
708 log::info!(
709 "Scrolling by {:?}, previous scroll top: {:?}",
710 delta,
711 scroll_top.unwrap()
712 );
713 state.0.borrow_mut().scroll(
714 scroll_top.as_ref().unwrap(),
715 height,
716 delta,
717 true,
718 &mut presenter.build_event_context(cx),
719 );
720 }
721 30..=34 => {
722 width = rng.gen_range(0_f32..=1000_f32);
723 log::info!("changing width: {:?}", width);
724 }
725 35..=54 => {
726 height = rng.gen_range(0_f32..=1000_f32);
727 log::info!("changing height: {:?}", height);
728 }
729 _ => {
730 let mut elements = elements.borrow_mut();
731 let end_ix = rng.gen_range(0..=elements.len());
732 let start_ix = rng.gen_range(0..=end_ix);
733 let new_elements = (0..rng.gen_range(0..10))
734 .map(|_| {
735 let id = next_id;
736 next_id += 1;
737 (id, rng.gen_range(0_f32..=100_f32))
738 })
739 .collect::<Vec<_>>();
740 log::info!("splice({:?}, {:?})", start_ix..end_ix, new_elements);
741 state.splice(start_ix..end_ix, new_elements.len());
742 elements.splice(start_ix..end_ix, new_elements);
743 for (ix, item) in state.0.borrow().items.cursor::<(), ()>().enumerate() {
744 if let ListItem::Rendered(element) = item {
745 let (expected_id, _) = elements[ix];
746 element.with_metadata(|metadata: Option<&usize>| {
747 assert_eq!(*metadata.unwrap(), expected_id);
748 });
749 }
750 }
751 }
752 }
753
754 let mut list = List::new(state.clone());
755 let (size, new_scroll_top) = list.layout(
756 SizeConstraint::new(vec2f(0., 0.), vec2f(width, height)),
757 &mut presenter.build_layout_context(cx),
758 );
759 assert_eq!(size, vec2f(width, height));
760 scroll_top = Some(new_scroll_top);
761
762 let state = state.0.borrow();
763 let rendered_top = (state.scroll_top(height) - overdraw).max(0.);
764 let rendered_bottom = state.scroll_top(height) + height + overdraw;
765 let mut item_top = 0.;
766 log::info!(
767 "rendered top {:?}, rendered bottom {:?}",
768 rendered_top,
769 rendered_bottom
770 );
771 log::info!("items {:?}", state.items.items(&()));
772 assert_eq!(state.items.summary().count, elements.borrow().len());
773 for (ix, item) in state.items.cursor::<(), ()>().enumerate() {
774 match item {
775 ListItem::Unrendered => {
776 let item_bottom = item_top;
777 assert!(item_bottom <= rendered_top || item_top >= rendered_bottom);
778 item_top = item_bottom;
779 }
780 ListItem::Removed(height) => {
781 let (id, expected_height) = elements.borrow()[ix];
782 assert_eq!(
783 *height, expected_height,
784 "element {} height didn't match",
785 id
786 );
787 let item_bottom = item_top + height;
788 assert!(item_bottom <= rendered_top || item_top >= rendered_bottom);
789 item_top = item_bottom;
790 }
791 ListItem::Rendered(element) => {
792 let (expected_id, expected_height) = elements.borrow()[ix];
793 element.with_metadata(|metadata: Option<&usize>| {
794 assert_eq!(*metadata.unwrap(), expected_id);
795 });
796 assert_eq!(element.size().y(), expected_height);
797 let item_bottom = item_top + element.size().y();
798 assert!(item_bottom > rendered_top || item_top < rendered_bottom);
799 item_top = item_bottom;
800 }
801 }
802 }
803 }
804 }
805
806 struct TestElement {
807 id: usize,
808 size: Vector2F,
809 }
810
811 impl TestElement {
812 fn new(id: usize, height: f32) -> Self {
813 Self {
814 id,
815 size: vec2f(100., height),
816 }
817 }
818 }
819
820 impl Element for TestElement {
821 type LayoutState = ();
822 type PaintState = ();
823
824 fn layout(&mut self, _: SizeConstraint, _: &mut LayoutContext) -> (Vector2F, ()) {
825 (self.size, ())
826 }
827
828 fn paint(&mut self, _: RectF, _: &mut (), _: &mut PaintContext) {
829 todo!()
830 }
831
832 fn dispatch_event(
833 &mut self,
834 _: &Event,
835 _: RectF,
836 _: &mut (),
837 _: &mut (),
838 _: &mut EventContext,
839 ) -> bool {
840 todo!()
841 }
842
843 fn debug(&self, _: RectF, _: &(), _: &(), _: &DebugContext) -> serde_json::Value {
844 self.id.into()
845 }
846
847 fn metadata(&self) -> Option<&dyn std::any::Any> {
848 Some(&self.id)
849 }
850 }
851}