list.rs

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