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