Skip to main content

inset_rendering/
sliver_list.rs

1//! Flutter counterpart: `rendering/sliver_list.dart`.
2
3use std::rc::Rc;
4
5use inset_embedder::{Matrix4, Offset};
6use inset_foundation::{App, Handle, PRECISION_ERROR_TOLERANCE};
7
8use crate::box_::AnyRenderBox;
9use crate::object::{
10    AnyRenderObject, ContainerRenderObjectData, ContainerRenderObjectMixin, RenderHandle,
11    RenderObject, RenderObjectData,
12};
13use crate::painting_context::PaintingContext;
14use crate::pipeline_owner::PipelineOwner;
15use crate::sliver::{
16    RenderSliver, RenderSliverData, RenderSliverHelpers, SliverGeometry, SliverHitTestResult,
17};
18use crate::sliver_multi_box_adaptor::{
19    RenderSliverBoxChildManager, RenderSliverMultiBoxAdaptor, RenderSliverMultiBoxAdaptorData,
20    RenderSliverWithKeepAliveMixin, SliverMultiBoxAdaptorParentData,
21};
22
23/// A sliver that places multiple box children in a linear array along the main axis.
24///
25/// Each child is forced to have the [`crate::SliverConstraints::cross_axis_extent`] in the cross
26/// axis but determines its own main axis extent.
27///
28/// [`RenderSliverList`] determines its scroll offset by "dead reckoning" because children outside
29/// the visible part of the sliver are not materialized, which means [`RenderSliverList`] cannot
30/// learn their main axis extent. Instead, newly materialized children are placed adjacent to
31/// existing children.
32pub struct RenderSliverList {
33    render_object: RenderObjectData,
34    render_sliver: RenderSliverData,
35    container: ContainerRenderObjectData<AnyRenderBox>,
36    adaptor: RenderSliverMultiBoxAdaptorData,
37}
38
39impl RenderSliverList {
40    /// Creates a sliver that places multiple box children in a linear array along the main axis.
41    pub fn new(
42        app: &mut App,
43        child_manager: Rc<dyn RenderSliverBoxChildManager>,
44    ) -> RenderHandle<RenderSliverList> {
45        RenderHandle::new_sliver(
46            app,
47            RenderSliverList {
48                render_object: RenderObjectData::new(),
49                render_sliver: RenderSliverData::new(),
50                container: ContainerRenderObjectData::new(),
51                adaptor: RenderSliverMultiBoxAdaptorData::new(child_manager),
52            },
53        )
54    }
55}
56
57impl ContainerRenderObjectMixin for RenderSliverList {
58    type ChildType = AnyRenderBox;
59    type ParentDataType = SliverMultiBoxAdaptorParentData;
60
61    fn container_data(
62        self: RenderHandle<Self>,
63        app: &App,
64    ) -> &ContainerRenderObjectData<AnyRenderBox> {
65        &self.get(app).container
66    }
67
68    fn container_data_mut(
69        self: RenderHandle<Self>,
70        app: &mut App,
71    ) -> &mut ContainerRenderObjectData<AnyRenderBox> {
72        &mut self.get_mut(app).container
73    }
74
75    fn insert(
76        self: RenderHandle<Self>,
77        app: &mut App,
78        child: AnyRenderBox,
79        after: Option<AnyRenderBox>,
80    ) {
81        RenderSliverMultiBoxAdaptor::insert(self, app, child, after)
82    }
83
84    fn move_child(
85        self: RenderHandle<Self>,
86        app: &mut App,
87        child: AnyRenderBox,
88        after: Option<AnyRenderBox>,
89    ) {
90        RenderSliverMultiBoxAdaptor::move_child(self, app, child, after)
91    }
92
93    fn remove(self: RenderHandle<Self>, app: &mut App, child: AnyRenderBox) {
94        RenderSliverMultiBoxAdaptor::remove(self, app, child)
95    }
96
97    fn remove_all(self: RenderHandle<Self>, app: &mut App) {
98        RenderSliverMultiBoxAdaptor::remove_all(self, app)
99    }
100}
101
102impl RenderSliverHelpers for RenderSliverList {}
103
104impl RenderSliverWithKeepAliveMixin for RenderSliverList {}
105
106impl RenderSliverMultiBoxAdaptor for RenderSliverList {
107    crate::render_sliver_multi_box_adaptor_accessors!();
108}
109
110impl RenderObject for RenderSliverList {
111    crate::render_object_accessors!();
112
113    fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
114        RenderSliverMultiBoxAdaptor::setup_parent_data(self, app, child);
115        RenderSliverWithKeepAliveMixin::setup_parent_data(self, app, child);
116    }
117
118    fn visit_children(
119        self: RenderHandle<Self>,
120        app: &App,
121        visitor: &mut dyn FnMut(AnyRenderObject),
122    ) {
123        RenderSliverMultiBoxAdaptor::visit_children(self, app, visitor)
124    }
125
126    fn did_attach(self: RenderHandle<Self>, app: &mut App, owner: Handle<PipelineOwner>) {
127        RenderSliverMultiBoxAdaptor::did_attach(self, app, owner)
128    }
129
130    fn did_detach(self: RenderHandle<Self>, app: &mut App) {
131        RenderSliverMultiBoxAdaptor::did_detach(self, app)
132    }
133
134    fn redepth_children(self: RenderHandle<Self>, app: &mut App) {
135        RenderSliverMultiBoxAdaptor::redepth_children(self, app)
136    }
137
138    fn apply_paint_transform(
139        self: RenderHandle<Self>,
140        app: &App,
141        child: AnyRenderObject,
142        transform: &mut Matrix4,
143    ) {
144        RenderSliverMultiBoxAdaptor::apply_paint_transform(self, app, child, transform)
145    }
146
147    fn paint(
148        self: RenderHandle<Self>,
149        app: &mut App,
150        context: &mut PaintingContext,
151        offset: Offset,
152    ) {
153        RenderSliverMultiBoxAdaptor::paint(self, app, context, offset)
154    }
155
156    fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
157        let constraints = self.constraints(app);
158        let child_manager = self.child_manager(app);
159        child_manager.did_start_layout(app);
160        child_manager.set_did_underflow(app, false);
161
162        let scroll_offset = constraints.scroll_offset + constraints.cache_origin;
163        debug_assert!(scroll_offset >= 0.0);
164        let remaining_extent = constraints.remaining_cache_extent;
165        debug_assert!(remaining_extent >= 0.0);
166        let target_end_scroll_offset = scroll_offset + remaining_extent;
167        let child_constraints = constraints.as_box_constraints(0.0, f64::INFINITY, None);
168        let mut leading_garbage = 0;
169        let mut trailing_garbage = 0;
170        let mut reached_end = false;
171
172        // This algorithm in principle is straight-forward: find the first child that overlaps the
173        // given scroll_offset, creating more children at the top of the list if necessary, then
174        // walk down the list updating and laying out each child and adding more at the end if
175        // necessary until we have enough children to cover the entire viewport.
176        //
177        // It is complicated by one minor issue, which is that any time you update or create a
178        // child, it's possible that some of the children that haven't yet been laid out will be
179        // removed, leaving the list in an inconsistent state, and requiring that missing nodes be
180        // recreated.
181        //
182        // To keep this mess tractable, this algorithm starts from what is currently the first
183        // child, if any, and then walks up and/or down from there, so that the nodes that might
184        // get removed are always at the edges of what has already been laid out.
185
186        // Make sure we have at least one child to start from.
187        if self.first_child(app).is_none() && !self.add_initial_child(app, 0, 0.0) {
188            // There are no children.
189            self.set_geometry(app, SliverGeometry::ZERO);
190            child_manager.did_finish_layout(app);
191            return;
192        }
193
194        // We have at least one child.
195
196        // These variables track the range of children that we have laid out. Within this range,
197        // the children have consecutive indices. Outside this range, it's possible for a child to
198        // get removed without notice.
199        let mut leading_child_with_layout: Option<AnyRenderBox> = None;
200        let mut trailing_child_with_layout: Option<AnyRenderBox> = None;
201
202        let mut earliest_useful_child = self.first_child(app);
203
204        // A first child with no layout offset is likely a result of children reordering.
205        //
206        // We rely on the first child to have an accurate layout offset. In the case of no layout
207        // offset, we have to find the first child that has a valid layout offset.
208        let first_child = self.first_child(app).expect("checked above");
209        if RenderSliver::child_scroll_offset(self, app, first_child.as_object()).is_none() {
210            let mut leading_children_without_layout_offset = 0;
211            while let Some(current) = earliest_useful_child {
212                if RenderSliver::child_scroll_offset(self, app, current.as_object()).is_some() {
213                    break;
214                }
215                earliest_useful_child = self.child_after(app, current);
216                leading_children_without_layout_offset += 1;
217            }
218            // We should be able to destroy children with no layout offset safely, because they
219            // are likely outside of the viewport.
220            self.collect_garbage(app, leading_children_without_layout_offset, 0);
221            // If we cannot find a valid layout offset, start from the initial child.
222            if self.first_child(app).is_none() && !self.add_initial_child(app, 0, 0.0) {
223                // There are no children.
224                self.set_geometry(app, SliverGeometry::ZERO);
225                child_manager.did_finish_layout(app);
226                return;
227            }
228        }
229
230        // Find the last child that is at or before the scroll offset.
231        earliest_useful_child = self.first_child(app);
232        loop {
233            let current = earliest_useful_child.expect("the child list is not empty");
234            let earliest_scroll_offset =
235                RenderSliver::child_scroll_offset(self, app, current.as_object())
236                    .expect("a laid-out child has a scroll offset");
237            if earliest_scroll_offset <= scroll_offset {
238                break;
239            }
240            // We have to add children before the earliest useful child.
241            earliest_useful_child =
242                self.insert_and_layout_leading_child(app, child_constraints, true);
243            let Some(inserted) = earliest_useful_child else {
244                let first_child = self.first_child(app).expect("the child list is not empty");
245                first_child
246                    .as_object()
247                    .parent_data_of_mut::<SliverMultiBoxAdaptorParentData>(app)
248                    .set_layout_offset(Some(0.0));
249
250                if scroll_offset == 0.0 {
251                    // insert_and_layout_leading_child only lays out the children before the first
252                    // child. In this case, nothing has been laid out. We have to lay out the
253                    // first child manually.
254                    first_child.layout(app, child_constraints, true);
255                    leading_child_with_layout = Some(first_child);
256                    trailing_child_with_layout.get_or_insert(first_child);
257                    break;
258                }
259                // We ran out of children before reaching the scroll offset. We must inform our
260                // parent that this sliver cannot fulfill its contract and that we need a scroll
261                // offset correction.
262                self.set_geometry(
263                    app,
264                    SliverGeometry::builder()
265                        .scroll_offset_correction(-scroll_offset)
266                        .build(),
267                );
268                return;
269            };
270
271            let first_child = self.first_child(app).expect("the child list is not empty");
272            let first_child_scroll_offset =
273                earliest_scroll_offset - self.paint_extent_of(app, first_child);
274            // first_child_scroll_offset may contain double precision error
275            if first_child_scroll_offset < -PRECISION_ERROR_TOLERANCE {
276                // Let's assume there is no child before the first child. We will correct it on
277                // the next layout if it is not.
278                self.set_geometry(
279                    app,
280                    SliverGeometry::builder()
281                        .scroll_offset_correction(-first_child_scroll_offset)
282                        .build(),
283                );
284                first_child
285                    .as_object()
286                    .parent_data_of_mut::<SliverMultiBoxAdaptorParentData>(app)
287                    .set_layout_offset(Some(0.0));
288                return;
289            }
290
291            inserted
292                .as_object()
293                .parent_data_of_mut::<SliverMultiBoxAdaptorParentData>(app)
294                .set_layout_offset(Some(first_child_scroll_offset));
295            debug_assert_eq!(Some(inserted), self.first_child(app));
296            leading_child_with_layout = Some(inserted);
297            trailing_child_with_layout.get_or_insert(inserted);
298        }
299
300        debug_assert!(
301            RenderSliver::child_scroll_offset(
302                self,
303                app,
304                self.first_child(app).expect("not empty").as_object()
305            )
306            .expect("laid out")
307                > -PRECISION_ERROR_TOLERANCE
308        );
309
310        // If the scroll offset is at zero, we should make sure we are actually at the beginning
311        // of the list.
312        if scroll_offset < PRECISION_ERROR_TOLERANCE {
313            // We iterate from the first child in case the leading child has a 0 paint extent.
314            loop {
315                let first_child = self.first_child(app).expect("the child list is not empty");
316                if self.index_of(app, first_child) <= 0 {
317                    break;
318                }
319                let earliest_scroll_offset =
320                    RenderSliver::child_scroll_offset(self, app, first_child.as_object())
321                        .expect("laid out");
322                // We correct one child at a time. If there are more children before the earliest
323                // useful child, we will correct it once the scroll offset reaches zero again.
324                let inserted = self.insert_and_layout_leading_child(app, child_constraints, true);
325                debug_assert!(inserted.is_some());
326                let first_child = self.first_child(app).expect("the child list is not empty");
327                let first_child_scroll_offset =
328                    earliest_scroll_offset - self.paint_extent_of(app, first_child);
329                first_child
330                    .as_object()
331                    .parent_data_of_mut::<SliverMultiBoxAdaptorParentData>(app)
332                    .set_layout_offset(Some(0.0));
333                // We only need to correct if the leading child actually has a paint extent.
334                if first_child_scroll_offset < -PRECISION_ERROR_TOLERANCE {
335                    self.set_geometry(
336                        app,
337                        SliverGeometry::builder()
338                            .scroll_offset_correction(-first_child_scroll_offset)
339                            .build(),
340                    );
341                    return;
342                }
343            }
344        }
345
346        // At this point, the earliest useful child is the first child, and is a child whose
347        // scroll offset is at or before the scroll offset, and leading_child_with_layout and
348        // trailing_child_with_layout are either None or cover a range of render boxes that we
349        // have laid out with the first being the same as the earliest useful child and the last
350        // being either at or after the scroll offset.
351        let earliest_useful_child = self.first_child(app).expect("the child list is not empty");
352
353        // Make sure we've laid out at least one child. Dart also assigns
354        // `leadingChildWithLayout` here; nothing reads it after this point.
355        if leading_child_with_layout.is_none() {
356            earliest_useful_child.layout(app, child_constraints, true);
357            trailing_child_with_layout = Some(earliest_useful_child);
358        }
359
360        // Here, the earliest useful child is still the first child, it's got a scroll offset that
361        // is at or before our actual scroll offset, and it has been laid out, and is in fact our
362        // leading child with layout. It's possible that some children beyond that one have also
363        // been laid out.
364        let mut in_layout_range = true;
365        let mut child = Some(earliest_useful_child);
366        let mut index = self.index_of(app, earliest_useful_child);
367        let mut end_scroll_offset =
368            RenderSliver::child_scroll_offset(self, app, earliest_useful_child.as_object())
369                .expect("laid out")
370                + self.paint_extent_of(app, earliest_useful_child);
371
372        // Dart's local `advance()`, which is used in two different places below.
373        let advance = |app: &mut App,
374                       child: &mut Option<AnyRenderBox>,
375                       index: &mut i32,
376                       end_scroll_offset: &mut f64,
377                       in_layout_range: &mut bool,
378                       trailing_child_with_layout: &mut Option<AnyRenderBox>|
379         -> bool {
380            // returns true if we advanced, false if we have no more children
381            let current = child.expect("advance is only called with a child");
382            if Some(current) == *trailing_child_with_layout {
383                *in_layout_range = false;
384            }
385            *child = self.child_after(app, current);
386            if child.is_none() {
387                *in_layout_range = false;
388            }
389            *index += 1;
390            if !*in_layout_range {
391                if child.is_none_or(|current| self.index_of(app, current) != *index) {
392                    // We are missing a child. Insert it (and lay it out) if possible.
393                    *child = self.insert_and_layout_child(
394                        app,
395                        child_constraints,
396                        *trailing_child_with_layout,
397                        true,
398                    );
399                    if child.is_none() {
400                        // We have run out of children.
401                        return false;
402                    }
403                } else {
404                    // Lay out the child.
405                    child.expect("checked").layout(app, child_constraints, true);
406                }
407                *trailing_child_with_layout = *child;
408            }
409            let current = child.expect("set above");
410            let child_parent_data = current
411                .as_object()
412                .parent_data_of_mut::<SliverMultiBoxAdaptorParentData>(app);
413            child_parent_data.set_layout_offset(Some(*end_scroll_offset));
414            debug_assert_eq!(child_parent_data.index, Some(*index));
415            *end_scroll_offset = RenderSliver::child_scroll_offset(self, app, current.as_object())
416                .expect("just set")
417                + self.paint_extent_of(app, current);
418            true
419        };
420
421        // Find the first child that ends after the scroll offset.
422        while end_scroll_offset < scroll_offset {
423            leading_garbage += 1;
424            if !advance(
425                app,
426                &mut child,
427                &mut index,
428                &mut end_scroll_offset,
429                &mut in_layout_range,
430                &mut trailing_child_with_layout,
431            ) {
432                debug_assert_eq!(leading_garbage, self.child_count(app));
433                debug_assert!(child.is_none());
434                // we want to make sure we keep the last child around so we know the end scroll
435                // offset
436                self.collect_garbage(app, leading_garbage - 1, 0);
437                debug_assert_eq!(self.first_child(app), self.last_child(app));
438                let last_child = self.last_child(app).expect("one child is kept");
439                let extent = RenderSliver::child_scroll_offset(self, app, last_child.as_object())
440                    .expect("laid out")
441                    + self.paint_extent_of(app, last_child);
442                self.set_geometry(
443                    app,
444                    SliverGeometry::builder()
445                        .scroll_extent(extent)
446                        .max_paint_extent(extent)
447                        .build(),
448                );
449                return;
450            }
451        }
452
453        // Now find the first child that ends after our end.
454        while end_scroll_offset < target_end_scroll_offset {
455            if !advance(
456                app,
457                &mut child,
458                &mut index,
459                &mut end_scroll_offset,
460                &mut in_layout_range,
461                &mut trailing_child_with_layout,
462            ) {
463                reached_end = true;
464                break;
465            }
466        }
467
468        // Finally count up all the remaining children and label them as garbage.
469        if let Some(current) = child {
470            let mut walker = self.child_after(app, current);
471            while let Some(current) = walker {
472                trailing_garbage += 1;
473                walker = self.child_after(app, current);
474            }
475        }
476
477        // At this point everything should be good to go, we just have to clean up the garbage and
478        // report the geometry.
479        self.collect_garbage(app, leading_garbage, trailing_garbage);
480
481        debug_assert!(self.debug_assert_child_list_is_non_empty_and_contiguous(app));
482        let first_child = self.first_child(app).expect("not empty");
483        let last_child = self.last_child(app).expect("not empty");
484        let leading_scroll_offset =
485            RenderSliver::child_scroll_offset(self, app, first_child.as_object())
486                .expect("laid out");
487        let estimated_max_scroll_offset = if reached_end {
488            end_scroll_offset
489        } else {
490            let estimated = child_manager.estimate_max_scroll_offset(
491                app,
492                constraints,
493                Some(self.index_of(app, first_child)),
494                Some(self.index_of(app, last_child)),
495                Some(leading_scroll_offset),
496                Some(end_scroll_offset),
497            );
498            debug_assert!(estimated >= end_scroll_offset - leading_scroll_offset);
499            estimated
500        };
501        let paint_extent =
502            self.calculate_paint_offset(app, constraints, leading_scroll_offset, end_scroll_offset);
503        let cache_extent =
504            self.calculate_cache_offset(app, constraints, leading_scroll_offset, end_scroll_offset);
505        let target_end_scroll_offset_for_paint =
506            constraints.scroll_offset + constraints.remaining_paint_extent;
507        self.set_geometry(
508            app,
509            SliverGeometry::builder()
510                .scroll_extent(estimated_max_scroll_offset)
511                .paint_extent(paint_extent)
512                .cache_extent(cache_extent)
513                .max_paint_extent(estimated_max_scroll_offset)
514                // Conservative to avoid flickering away the clip during scroll.
515                .has_visual_overflow(
516                    end_scroll_offset > target_end_scroll_offset_for_paint
517                        || constraints.scroll_offset > 0.0,
518                )
519                .build(),
520        );
521
522        // We may have started the layout while scrolled to the end, which would not expose a new
523        // child.
524        if estimated_max_scroll_offset == end_scroll_offset {
525            child_manager.set_did_underflow(app, true);
526        }
527        child_manager.did_finish_layout(app);
528    }
529}
530
531impl RenderSliver for RenderSliverList {
532    crate::render_sliver_accessors!();
533
534    fn hit_test_children(
535        self: RenderHandle<Self>,
536        app: &mut App,
537        result: &mut SliverHitTestResult<'_>,
538        main_axis_position: f64,
539        cross_axis_position: f64,
540    ) -> bool {
541        RenderSliverMultiBoxAdaptor::hit_test_children(
542            self,
543            app,
544            result,
545            main_axis_position,
546            cross_axis_position,
547        )
548    }
549
550    fn child_main_axis_position(
551        self: RenderHandle<Self>,
552        app: &App,
553        child: AnyRenderObject,
554    ) -> f64 {
555        RenderSliverMultiBoxAdaptor::child_main_axis_position(self, app, child)
556    }
557
558    fn child_scroll_offset(
559        self: RenderHandle<Self>,
560        app: &App,
561        child: AnyRenderObject,
562    ) -> Option<f64> {
563        RenderSliverMultiBoxAdaptor::child_scroll_offset(self, app, child)
564    }
565}
566
567#[cfg(test)]
568mod tests {
569    use inset_embedder::Size;
570    use inset_foundation::AppCell;
571    use inset_gestures::HitTestResult;
572
573    use super::*;
574    use crate::sliver_multi_box_adaptor::test_support::TestChildManager;
575    use crate::sliver_multi_box_adaptor::viewport_test_support::ScrollHarness;
576
577    const VIEWPORT: Size = Size::new(100.0, 200.0);
578
579    fn sliver_list(
580        app: &mut App,
581        extents: Vec<f64>,
582    ) -> (
583        RenderHandle<RenderSliverList>,
584        Rc<TestChildManager<RenderSliverList>>,
585        ScrollHarness,
586    ) {
587        let manager = TestChildManager::new(extents);
588        let list = RenderSliverList::new(app, manager.clone());
589        manager.attach(list);
590        let harness = ScrollHarness::new(app, list.as_sliver(), VIEWPORT);
591        (list, manager, harness)
592    }
593
594    /// `sliver_list_test.dart`: children of differing extents are placed one after the other, and
595    /// each child's layout offset is the sum of the extents before it.
596    #[test]
597    fn a_sliver_list_stacks_children_of_differing_extents() {
598        let cell = AppCell::new();
599        let mut app = cell.borrow_mut();
600        let (list, _, _harness) = sliver_list(&mut app, vec![40.0, 60.0, 80.0, 100.0, 120.0]);
601
602        let first = list.first_child(&app).expect("children were created");
603        let second = list.child_after(&app, first).expect("two children");
604        let third = list.child_after(&app, second).expect("three children");
605        assert_eq!(
606            RenderSliver::child_scroll_offset(list, &app, first.as_object()),
607            Some(0.0)
608        );
609        assert_eq!(
610            RenderSliver::child_scroll_offset(list, &app, second.as_object()),
611            Some(40.0)
612        );
613        assert_eq!(
614            RenderSliver::child_scroll_offset(list, &app, third.as_object()),
615            Some(100.0)
616        );
617        assert_eq!(list.geometry(&app).paint_extent, 200.0);
618        assert!(list.geometry(&app).has_visual_overflow);
619    }
620
621    /// A list that fits inside the viewport reports its exact extent and no overflow.
622    #[test]
623    fn a_short_sliver_list_reports_its_exact_extent() {
624        let cell = AppCell::new();
625        let mut app = cell.borrow_mut();
626        let (list, manager, _harness) = sliver_list(&mut app, vec![40.0, 60.0]);
627
628        let geometry = list.geometry(&app);
629        assert_eq!(geometry.scroll_extent, 100.0);
630        assert_eq!(geometry.paint_extent, 100.0);
631        assert!(!geometry.has_visual_overflow);
632        assert_eq!(list.child_count(&app), 2);
633        assert!(manager.did_underflow.get());
634    }
635
636    /// Scrolling drops the children that left the cache area and creates the ones that entered.
637    #[test]
638    fn scrolling_a_sliver_list_replaces_its_children() {
639        let cell = AppCell::new();
640        let mut app = cell.borrow_mut();
641        let (list, _, harness) = sliver_list(&mut app, vec![100.0; 30]);
642        assert_eq!(
643            list.index_of(&app, list.first_child(&app).expect("built")),
644            0
645        );
646
647        harness.scroll_to(&mut app, 1500.0);
648
649        let first = list.first_child(&app).expect("children were created");
650        // 1500 - 250 of leading cache = 1250, which is inside index 12.
651        assert_eq!(list.index_of(&app, first), 12);
652        assert!(list.geometry(&app).has_visual_overflow);
653    }
654
655    /// A hit inside a reified child lands on the child and the sliver.
656    #[test]
657    fn a_sliver_list_hit_tests_its_children() {
658        let cell = AppCell::new();
659        let mut app = cell.borrow_mut();
660        let (list, _, _harness) = sliver_list(&mut app, vec![50.0; 20]);
661
662        let mut result = HitTestResult::new();
663        let hit = list.hit_test(
664            &mut app,
665            &mut SliverHitTestResult::wrap(&mut result),
666            120.0,
667            10.0,
668        );
669
670        assert!(hit);
671        assert_eq!(result.path().len(), 2);
672    }
673
674    /// A kept-alive child is moved to the bucket rather than destroyed when it scrolls away.
675    #[test]
676    fn a_sliver_list_keeps_a_child_alive() {
677        let cell = AppCell::new();
678        let mut app = cell.borrow_mut();
679        let (list, _, harness) = sliver_list(&mut app, vec![100.0; 30]);
680        let first = list.first_child(&app).expect("children were created");
681        first
682            .as_object()
683            .parent_data_of_mut::<SliverMultiBoxAdaptorParentData>(&mut app)
684            .set_keep_alive(true);
685
686        harness.scroll_to(&mut app, 2000.0);
687
688        assert_eq!(list.keep_alive_children(&app), vec![first]);
689        assert!(first.as_object().attached(&app));
690    }
691}