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