list.rs

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