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