list.rs

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