list.rs

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