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