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