Skip to main content

inset_rendering/
sliver_fixed_extent_list.rs

1//! Flutter counterpart: `rendering/sliver_fixed_extent_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, BoxConstraints};
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    ItemExtentBuilder, RenderSliver, RenderSliverData, RenderSliverHelpers, SliverConstraints,
17    SliverGeometry, SliverHitTestResult, SliverLayoutDimensions, calculate_cache_offset,
18    calculate_paint_offset,
19};
20use crate::sliver_multi_box_adaptor::{
21    RenderSliverBoxChildManager, RenderSliverMultiBoxAdaptor, RenderSliverMultiBoxAdaptorData,
22    RenderSliverWithKeepAliveMixin, SliverMultiBoxAdaptorParentData,
23};
24
25/// Flutter's `RenderSliverFixedExtentBoxAdaptor` field.
26#[derive(Debug, Default)]
27pub struct RenderSliverFixedExtentBoxAdaptorData {
28    current_layout_dimensions: Option<SliverLayoutDimensions>,
29}
30
31impl RenderSliverFixedExtentBoxAdaptorData {
32    /// Creates the state of a fixed-extent box adaptor that has not been laid out.
33    pub const fn new() -> RenderSliverFixedExtentBoxAdaptorData {
34        RenderSliverFixedExtentBoxAdaptorData {
35            current_layout_dimensions: None,
36        }
37    }
38}
39
40/// Implements [`RenderSliverFixedExtentBoxAdaptor`] field accessors for a `fixed_extent` field.
41#[macro_export]
42macro_rules! render_sliver_fixed_extent_accessors {
43    () => {
44        fn fixed_extent_data(
45            self: $crate::RenderHandle<Self>,
46            app: &::inset_foundation::App,
47        ) -> &$crate::RenderSliverFixedExtentBoxAdaptorData {
48            &self.get(app).fixed_extent
49        }
50        fn fixed_extent_data_mut(
51            self: $crate::RenderHandle<Self>,
52            app: &mut ::inset_foundation::App,
53        ) -> &mut $crate::RenderSliverFixedExtentBoxAdaptorData {
54            &mut self.get_mut(app).fixed_extent
55        }
56    };
57}
58
59/// A sliver that contains multiple box children that have the same extent in the main axis.
60///
61/// Flutter's `RenderSliverFixedExtentBoxAdaptor`. An implementor answers either
62/// [`item_extent`](Self::item_extent) or [`item_extent_builder`](Self::item_extent_builder).
63pub trait RenderSliverFixedExtentBoxAdaptor: RenderSliverMultiBoxAdaptor {
64    /// Mixin field access.
65    fn fixed_extent_data(
66        self: RenderHandle<Self>,
67        app: &App,
68    ) -> &RenderSliverFixedExtentBoxAdaptorData;
69
70    /// See [`fixed_extent_data`](Self::fixed_extent_data).
71    fn fixed_extent_data_mut(
72        self: RenderHandle<Self>,
73        app: &mut App,
74    ) -> &mut RenderSliverFixedExtentBoxAdaptorData;
75
76    /// The main-axis extent of each item.
77    ///
78    /// If this is `Some`, [`item_extent_builder`](Self::item_extent_builder) must be `None`.
79    /// If this is `None`, `item_extent_builder` must be `Some`.
80    fn item_extent(self: RenderHandle<Self>, app: &App) -> Option<f64>;
81
82    /// The main-axis extent builder of each item.
83    ///
84    /// If this is `Some`, [`item_extent`](Self::item_extent) must be `None`.
85    /// If this is `None`, `item_extent` must be `Some`.
86    fn item_extent_builder(self: RenderHandle<Self>, app: &App) -> Option<ItemExtentBuilder> {
87        let _ = app;
88        let _ = self;
89        None
90    }
91
92    /// The layout dimensions for the sliver.
93    ///
94    /// If the sliver has not been laid out yet, this returns a [`SliverLayoutDimensions`] based
95    /// on the current constraints.
96    fn layout_dimensions(self: RenderHandle<Self>, app: &App) -> SliverLayoutDimensions {
97        self.fixed_extent_data(app)
98            .current_layout_dimensions
99            .unwrap_or_else(|| {
100                let constraints = self.constraints(app);
101                SliverLayoutDimensions::new(
102                    constraints.scroll_offset,
103                    constraints.preceding_scroll_extent,
104                    constraints.viewport_main_axis_extent,
105                    constraints.cross_axis_extent,
106                )
107            })
108    }
109
110    /// The layout offset for the child with the given index.
111    ///
112    /// This function uses the returned value of
113    /// [`item_extent_builder`](Self::item_extent_builder) or the
114    /// [`item_extent`](Self::item_extent) to avoid recomputing item size repeatedly during
115    /// layout.
116    ///
117    /// By default, places the children in order, without gaps, starting from layout offset zero.
118    fn index_to_layout_offset(self: RenderHandle<Self>, app: &App, index: i32) -> f64 {
119        let Some(builder) = self.item_extent_builder(app) else {
120            let item_extent = self.item_extent(app).expect("one of the two is set");
121            return item_extent * f64::from(index);
122        };
123        let mut offset = 0.0;
124        let dimensions = self.layout_dimensions(app);
125        let child_manager = self.child_manager(app);
126        for i in 0..index {
127            let child_count = child_manager.estimated_child_count(app);
128            if child_count.is_some_and(|count| i > count - 1) {
129                break;
130            }
131            let Some(item_extent) = builder(i, dimensions) else {
132                break;
133            };
134            offset += item_extent;
135        }
136        offset
137    }
138
139    /// The minimum child index that is visible at the given scroll offset.
140    ///
141    /// By default, returns a value consistent with the children being placed in order, without
142    /// gaps, starting from layout offset zero.
143    fn get_min_child_index_for_scroll_offset(
144        self: RenderHandle<Self>,
145        app: &App,
146        scroll_offset: f64,
147    ) -> i32 {
148        let Some(builder) = self.item_extent_builder(app) else {
149            let item_extent = self.item_extent(app).expect("one of the two is set");
150            if item_extent <= 0.0 {
151                return 0;
152            }
153            let actual = scroll_offset / item_extent;
154            let round = actual.round();
155            if (actual * item_extent - round * item_extent).abs() < PRECISION_ERROR_TOLERANCE {
156                return round as i32;
157            }
158            return actual.floor() as i32;
159        };
160        self.child_index_for_scroll_offset(app, scroll_offset, &builder)
161    }
162
163    /// The maximum child index that is visible at the given scroll offset.
164    ///
165    /// By default, returns a value consistent with the children being placed in order, without
166    /// gaps, starting from layout offset zero.
167    fn get_max_child_index_for_scroll_offset(
168        self: RenderHandle<Self>,
169        app: &App,
170        scroll_offset: f64,
171    ) -> i32 {
172        let Some(builder) = self.item_extent_builder(app) else {
173            let item_extent = self.item_extent(app).expect("one of the two is set");
174            if item_extent <= 0.0 {
175                return 0;
176            }
177            let actual = scroll_offset / item_extent - 1.0;
178            let round = actual.round();
179            if (actual * item_extent - round * item_extent).abs() < PRECISION_ERROR_TOLERANCE {
180                return (round as i32).max(0);
181            }
182            return (actual.ceil() as i32).max(0);
183        };
184        self.child_index_for_scroll_offset(app, scroll_offset, &builder)
185    }
186
187    /// Flutter's `_getChildIndexForScrollOffset`.
188    fn child_index_for_scroll_offset(
189        self: RenderHandle<Self>,
190        app: &App,
191        scroll_offset: f64,
192        builder: &ItemExtentBuilder,
193    ) -> i32 {
194        if scroll_offset == 0.0 {
195            return 0;
196        }
197        let dimensions = self.layout_dimensions(app);
198        let child_manager = self.child_manager(app);
199        let mut position = 0.0;
200        let mut index = 0;
201        while position < scroll_offset {
202            let child_count = child_manager.estimated_child_count(app);
203            if child_count.is_some_and(|count| index > count - 1) {
204                break;
205            }
206            let Some(item_extent) = builder(index, dimensions) else {
207                break;
208            };
209            position += item_extent;
210            index += 1;
211        }
212        index - 1
213    }
214
215    /// Called to estimate the total scrollable extents of this object.
216    ///
217    /// Must return the total distance from the start of the child with the earliest possible
218    /// index to the end of the child with the last possible index.
219    ///
220    /// By default, defers to [`RenderSliverBoxChildManager::estimate_max_scroll_offset`].
221    fn estimate_max_scroll_offset(
222        self: RenderHandle<Self>,
223        app: &App,
224        constraints: SliverConstraints,
225        first_index: Option<i32>,
226        last_index: Option<i32>,
227        leading_scroll_offset: Option<f64>,
228        trailing_scroll_offset: Option<f64>,
229    ) -> f64 {
230        self.child_manager(app).estimate_max_scroll_offset(
231            app,
232            constraints,
233            first_index,
234            last_index,
235            leading_scroll_offset,
236            trailing_scroll_offset,
237        )
238    }
239
240    /// Called to obtain a precise measure of the total scrollable extents of this object.
241    ///
242    /// This is used when no child is available for the index corresponding to the current scroll
243    /// offset, to determine the precise dimensions of the sliver.
244    ///
245    /// If [`item_extent_builder`](Self::item_extent_builder) is `None`, multiplies the
246    /// [`item_extent`](Self::item_extent) by the number of children reported by
247    /// [`RenderSliverBoxChildManager::child_count`]. Otherwise, sums the extents of the first
248    /// `child_count` children.
249    fn compute_max_scroll_offset(
250        self: RenderHandle<Self>,
251        app: &mut App,
252        _constraints: SliverConstraints,
253    ) -> f64 {
254        let child_manager = self.child_manager(app);
255        let Some(builder) = self.item_extent_builder(app) else {
256            let item_extent = self.item_extent(app).expect("one of the two is set");
257            return f64::from(child_manager.child_count(app)) * item_extent;
258        };
259        let dimensions = self.layout_dimensions(app);
260        let mut offset = 0.0;
261        for i in 0..child_manager.child_count(app) {
262            let Some(item_extent) = builder(i, dimensions) else {
263                break;
264            };
265            offset += item_extent;
266        }
267        offset
268    }
269
270    /// Flutter's `_getChildConstraints`.
271    fn child_constraints(self: RenderHandle<Self>, app: &App, index: i32) -> BoxConstraints {
272        let extent = match self.item_extent_builder(app) {
273            None => self.item_extent(app).expect("one of the two is set"),
274            Some(builder) => builder(index, self.layout_dimensions(app))
275                .expect("the builder must report an extent for a reified child"),
276        };
277        self.constraints(app)
278            .as_box_constraints(extent, extent, None)
279    }
280
281    /// The body of Flutter's `paintExtentOf` override.
282    fn paint_extent_of(self: RenderHandle<Self>, app: &App, child: AnyRenderBox) -> f64 {
283        match self.item_extent_builder(app) {
284            None => self.item_extent(app).expect("one of the two is set"),
285            Some(builder) => builder(self.index_of(app, child), self.layout_dimensions(app))
286                .expect("the builder must report an extent for a reified child"),
287        }
288    }
289
290    /// The body of Flutter's `performLayout` override.
291    fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
292        debug_assert!(
293            self.item_extent(app).is_some() != self.item_extent_builder(app).is_some(),
294            "exactly one of item_extent and item_extent_builder must be set"
295        );
296        debug_assert!(
297            self.item_extent_builder(app).is_some()
298                || self
299                    .item_extent(app)
300                    .is_some_and(|extent| extent.is_finite() && extent >= 0.0)
301        );
302
303        let constraints = self.constraints(app);
304        let child_manager = self.child_manager(app);
305        child_manager.did_start_layout(app);
306        child_manager.set_did_underflow(app, false);
307
308        let scroll_offset = constraints.scroll_offset + constraints.cache_origin;
309        debug_assert!(scroll_offset >= 0.0);
310        let remaining_extent = constraints.remaining_cache_extent;
311        debug_assert!(remaining_extent >= 0.0);
312        let target_end_scroll_offset = scroll_offset + remaining_extent;
313
314        self.fixed_extent_data_mut(app).current_layout_dimensions =
315            Some(SliverLayoutDimensions::new(
316                constraints.scroll_offset,
317                constraints.preceding_scroll_extent,
318                constraints.viewport_main_axis_extent,
319                constraints.cross_axis_extent,
320            ));
321
322        let first_index = self.get_min_child_index_for_scroll_offset(app, scroll_offset);
323        let target_last_index = target_end_scroll_offset
324            .is_finite()
325            .then(|| self.get_max_child_index_for_scroll_offset(app, target_end_scroll_offset));
326
327        if self.first_child(app).is_some() {
328            let leading_garbage = self.calculate_leading_garbage(app, first_index);
329            let trailing_garbage = match target_last_index {
330                Some(last_index) => self.calculate_trailing_garbage(app, last_index),
331                None => 0,
332            };
333            self.collect_garbage(app, leading_garbage, trailing_garbage);
334        } else {
335            self.collect_garbage(app, 0, 0);
336        }
337
338        if self.first_child(app).is_none() {
339            let layout_offset = self.index_to_layout_offset(app, first_index);
340            if !self.add_initial_child(app, first_index, layout_offset) {
341                // There are either no children, or we are past the end of all our children.
342                let max = if first_index <= 0 {
343                    0.0
344                } else {
345                    self.compute_max_scroll_offset(app, constraints)
346                };
347                self.set_geometry(
348                    app,
349                    SliverGeometry::new()
350                        .scroll_extent(max)
351                        .max_paint_extent(max),
352                );
353                child_manager.did_finish_layout(app);
354                return;
355            }
356        }
357
358        let mut trailing_child_with_layout: Option<AnyRenderBox> = None;
359
360        let first_child = self.first_child(app).expect("added above");
361        let mut index = self.index_of(app, first_child) - 1;
362        while index >= first_index {
363            let child_constraints = self.child_constraints(app, index);
364            let Some(child) = self.insert_and_layout_leading_child(app, child_constraints, false)
365            else {
366                // Items before the previously first child are no longer present. Reset the scroll
367                // offset to offset all items prior and up to the missing item. Let the parent
368                // re-lay-out everything.
369                let correction = self.index_to_layout_offset(app, index);
370                self.set_geometry(
371                    app,
372                    SliverGeometry::new().scroll_offset_correction(correction),
373                );
374                return;
375            };
376            let layout_offset = self.index_to_layout_offset(app, index);
377            let child_parent_data = child
378                .as_object()
379                .parent_data_of_mut::<SliverMultiBoxAdaptorParentData>(app);
380            child_parent_data.set_layout_offset(Some(layout_offset));
381            debug_assert_eq!(child_parent_data.index, Some(index));
382            trailing_child_with_layout.get_or_insert(child);
383            index -= 1;
384        }
385
386        if trailing_child_with_layout.is_none() {
387            let first_child = self.first_child(app).expect("the child list is not empty");
388            let child_constraints = self.child_constraints(app, self.index_of(app, first_child));
389            first_child.layout(app, child_constraints, false);
390            let layout_offset = self.index_to_layout_offset(app, first_index);
391            first_child
392                .as_object()
393                .parent_data_of_mut::<SliverMultiBoxAdaptorParentData>(app)
394                .set_layout_offset(Some(layout_offset));
395            trailing_child_with_layout = Some(first_child);
396        }
397
398        let mut estimated_max_scroll_offset = f64::INFINITY;
399        let mut index = self.index_of(app, trailing_child_with_layout.expect("set above")) + 1;
400        while target_last_index.is_none_or(|last_index| index <= last_index) {
401            let after = trailing_child_with_layout.expect("set above");
402            let mut child = self.child_after(app, after);
403            if child.is_none_or(|current| self.index_of(app, current) != index) {
404                let child_constraints = self.child_constraints(app, index);
405                child = self.insert_and_layout_child(app, child_constraints, Some(after), false);
406                if child.is_none() {
407                    // We have run out of children.
408                    estimated_max_scroll_offset = self.index_to_layout_offset(app, index);
409                    break;
410                }
411            } else {
412                let child_constraints = self.child_constraints(app, index);
413                child
414                    .expect("checked")
415                    .layout(app, child_constraints, false);
416            }
417            let child = child.expect("set above");
418            trailing_child_with_layout = Some(child);
419            debug_assert_eq!(
420                child
421                    .as_object()
422                    .parent_data_of::<SliverMultiBoxAdaptorParentData>(app)
423                    .index,
424                Some(index)
425            );
426            let layout_offset = self.index_to_layout_offset(app, index);
427            child
428                .as_object()
429                .parent_data_of_mut::<SliverMultiBoxAdaptorParentData>(app)
430                .set_layout_offset(Some(layout_offset));
431            index += 1;
432        }
433
434        let last_child = self.last_child(app).expect("the child list is not empty");
435        let last_index = self.index_of(app, last_child);
436        let leading_scroll_offset = self.index_to_layout_offset(app, first_index);
437        let trailing_scroll_offset = self.index_to_layout_offset(app, last_index + 1);
438
439        debug_assert!(
440            first_index == 0
441                || RenderSliver::child_scroll_offset(
442                    self,
443                    app,
444                    self.first_child(app).expect("not empty").as_object()
445                )
446                .expect("laid out")
447                    - scroll_offset
448                    <= PRECISION_ERROR_TOLERANCE
449        );
450        debug_assert!(self.debug_assert_child_list_is_non_empty_and_contiguous(app));
451        debug_assert_eq!(
452            self.index_of(app, self.first_child(app).expect("not empty")),
453            first_index
454        );
455        debug_assert!(target_last_index.is_none_or(|target| last_index <= target));
456
457        estimated_max_scroll_offset =
458            estimated_max_scroll_offset.min(self.estimate_max_scroll_offset(
459                app,
460                constraints,
461                Some(first_index),
462                Some(last_index),
463                Some(leading_scroll_offset),
464                Some(trailing_scroll_offset),
465            ));
466
467        let paint_extent =
468            calculate_paint_offset(constraints, leading_scroll_offset, trailing_scroll_offset);
469        let cache_extent =
470            calculate_cache_offset(constraints, leading_scroll_offset, trailing_scroll_offset);
471
472        let target_end_scroll_offset_for_paint =
473            constraints.scroll_offset + constraints.remaining_paint_extent;
474        let target_last_index_for_paint =
475            target_end_scroll_offset_for_paint.is_finite().then(|| {
476                self.get_max_child_index_for_scroll_offset(app, target_end_scroll_offset_for_paint)
477            });
478
479        self.set_geometry(
480            app,
481            SliverGeometry::new()
482                .scroll_extent(estimated_max_scroll_offset)
483                .paint_extent(paint_extent)
484                .cache_extent(cache_extent)
485                .max_paint_extent(estimated_max_scroll_offset)
486                // Conservative to avoid flickering away the clip during scroll.
487                .has_visual_overflow(
488                    target_last_index_for_paint.is_some_and(|target| last_index >= target)
489                        || constraints.scroll_offset > 0.0,
490                ),
491        );
492
493        // We may have started the layout while scrolled to the end, which would not expose a new
494        // child.
495        if estimated_max_scroll_offset == trailing_scroll_offset {
496            child_manager.set_did_underflow(app, true);
497        }
498        child_manager.did_finish_layout(app);
499    }
500}
501
502/// Declares the [`ContainerRenderObjectMixin`], [`RenderObject`] and [`RenderSliver`] impls that
503/// a [`RenderSliverFixedExtentBoxAdaptor`] leaf forwards to its base traits.
504macro_rules! fixed_extent_box_adaptor_leaf {
505    ($leaf:ty) => {
506        impl ContainerRenderObjectMixin for $leaf {
507            type ChildType = AnyRenderBox;
508            type ParentDataType = SliverMultiBoxAdaptorParentData;
509
510            fn container_data(
511                self: RenderHandle<Self>,
512                app: &App,
513            ) -> &ContainerRenderObjectData<AnyRenderBox> {
514                &self.get(app).container
515            }
516
517            fn container_data_mut(
518                self: RenderHandle<Self>,
519                app: &mut App,
520            ) -> &mut ContainerRenderObjectData<AnyRenderBox> {
521                &mut self.get_mut(app).container
522            }
523
524            fn insert(
525                self: RenderHandle<Self>,
526                app: &mut App,
527                child: AnyRenderBox,
528                after: Option<AnyRenderBox>,
529            ) {
530                RenderSliverMultiBoxAdaptor::insert(self, app, child, after)
531            }
532
533            fn move_child(
534                self: RenderHandle<Self>,
535                app: &mut App,
536                child: AnyRenderBox,
537                after: Option<AnyRenderBox>,
538            ) {
539                RenderSliverMultiBoxAdaptor::move_child(self, app, child, after)
540            }
541
542            fn remove(self: RenderHandle<Self>, app: &mut App, child: AnyRenderBox) {
543                RenderSliverMultiBoxAdaptor::remove(self, app, child)
544            }
545
546            fn remove_all(self: RenderHandle<Self>, app: &mut App) {
547                RenderSliverMultiBoxAdaptor::remove_all(self, app)
548            }
549        }
550
551        impl RenderSliverHelpers for $leaf {}
552
553        impl RenderSliverWithKeepAliveMixin for $leaf {}
554
555        impl RenderSliverMultiBoxAdaptor for $leaf {
556            crate::render_sliver_multi_box_adaptor_accessors!();
557
558            fn paint_extent_of(self: RenderHandle<Self>, app: &App, child: AnyRenderBox) -> f64 {
559                RenderSliverFixedExtentBoxAdaptor::paint_extent_of(self, app, child)
560            }
561        }
562
563        impl RenderObject for $leaf {
564            crate::render_object_accessors!();
565
566            fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
567                RenderSliverMultiBoxAdaptor::setup_parent_data(self, app, child);
568                RenderSliverWithKeepAliveMixin::setup_parent_data(self, app, child);
569            }
570
571            fn visit_children(
572                self: RenderHandle<Self>,
573                app: &App,
574                visitor: &mut dyn FnMut(AnyRenderObject),
575            ) {
576                RenderSliverMultiBoxAdaptor::visit_children(self, app, visitor)
577            }
578
579            fn did_attach(self: RenderHandle<Self>, app: &mut App, owner: Handle<PipelineOwner>) {
580                RenderSliverMultiBoxAdaptor::did_attach(self, app, owner)
581            }
582
583            fn did_detach(self: RenderHandle<Self>, app: &mut App) {
584                RenderSliverMultiBoxAdaptor::did_detach(self, app)
585            }
586
587            fn redepth_children(self: RenderHandle<Self>, app: &mut App) {
588                RenderSliverMultiBoxAdaptor::redepth_children(self, app)
589            }
590
591            fn apply_paint_transform(
592                self: RenderHandle<Self>,
593                app: &App,
594                child: AnyRenderObject,
595                transform: &mut Matrix4,
596            ) {
597                RenderSliverMultiBoxAdaptor::apply_paint_transform(self, app, child, transform)
598            }
599
600            fn paint(
601                self: RenderHandle<Self>,
602                app: &mut App,
603                context: &mut PaintingContext,
604                offset: Offset,
605            ) {
606                RenderSliverMultiBoxAdaptor::paint(self, app, context, offset)
607            }
608
609            fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
610                RenderSliverFixedExtentBoxAdaptor::perform_layout(self, app)
611            }
612        }
613
614        impl RenderSliver for $leaf {
615            crate::render_sliver_accessors!();
616
617            fn hit_test_children(
618                self: RenderHandle<Self>,
619                app: &mut App,
620                result: &mut SliverHitTestResult<'_>,
621                main_axis_position: f64,
622                cross_axis_position: f64,
623            ) -> bool {
624                RenderSliverMultiBoxAdaptor::hit_test_children(
625                    self,
626                    app,
627                    result,
628                    main_axis_position,
629                    cross_axis_position,
630                )
631            }
632
633            fn child_main_axis_position(
634                self: RenderHandle<Self>,
635                app: &App,
636                child: AnyRenderObject,
637            ) -> f64 {
638                RenderSliverMultiBoxAdaptor::child_main_axis_position(self, app, child)
639            }
640
641            fn child_scroll_offset(
642                self: RenderHandle<Self>,
643                app: &App,
644                child: AnyRenderObject,
645            ) -> Option<f64> {
646                RenderSliverMultiBoxAdaptor::child_scroll_offset(self, app, child)
647            }
648        }
649    };
650}
651
652/// A sliver that places multiple box children with the same main axis extent in a linear array.
653///
654/// [`RenderSliverFixedExtentList`] places its children in a linear array along the main axis
655/// starting at offset zero and without gaps. Each child is forced to have the
656/// [`item_extent`](Self::item_extent) in the main axis and the
657/// [`crate::SliverConstraints::cross_axis_extent`] in the cross axis.
658///
659/// [`RenderSliverFixedExtentList`] is more efficient than [`crate::RenderSliverList`] because it
660/// does not need to perform layout on its children to obtain their extent in the main axis.
661pub struct RenderSliverFixedExtentList {
662    render_object: RenderObjectData,
663    render_sliver: RenderSliverData,
664    container: ContainerRenderObjectData<AnyRenderBox>,
665    adaptor: RenderSliverMultiBoxAdaptorData,
666    fixed_extent: RenderSliverFixedExtentBoxAdaptorData,
667    item_extent: f64,
668}
669
670impl RenderSliverFixedExtentList {
671    /// Creates a sliver that contains multiple box children that have a given extent in the main
672    /// axis.
673    pub fn new(
674        app: &mut App,
675        child_manager: Rc<dyn RenderSliverBoxChildManager>,
676        item_extent: f64,
677    ) -> RenderHandle<RenderSliverFixedExtentList> {
678        RenderHandle::new_sliver(
679            app,
680            RenderSliverFixedExtentList {
681                render_object: RenderObjectData::new(),
682                render_sliver: RenderSliverData::new(),
683                container: ContainerRenderObjectData::new(),
684                adaptor: RenderSliverMultiBoxAdaptorData::new(child_manager),
685                fixed_extent: RenderSliverFixedExtentBoxAdaptorData::new(),
686                item_extent,
687            },
688        )
689    }
690
691    /// Sets [`RenderSliverFixedExtentBoxAdaptor::item_extent`].
692    pub fn set_item_extent(self: RenderHandle<Self>, app: &mut App, value: f64) {
693        if self.get(app).item_extent == value {
694            return;
695        }
696        self.get_mut(app).item_extent = value;
697        self.mark_needs_layout(app);
698    }
699}
700
701impl RenderSliverFixedExtentBoxAdaptor for RenderSliverFixedExtentList {
702    crate::render_sliver_fixed_extent_accessors!();
703
704    fn item_extent(self: RenderHandle<Self>, app: &App) -> Option<f64> {
705        Some(self.get(app).item_extent)
706    }
707}
708
709fixed_extent_box_adaptor_leaf!(RenderSliverFixedExtentList);
710
711/// A sliver that places multiple box children with the corresponding main axis extent in a linear
712/// array.
713pub struct RenderSliverVariedExtentList {
714    render_object: RenderObjectData,
715    render_sliver: RenderSliverData,
716    container: ContainerRenderObjectData<AnyRenderBox>,
717    adaptor: RenderSliverMultiBoxAdaptorData,
718    fixed_extent: RenderSliverFixedExtentBoxAdaptorData,
719    item_extent_builder: ItemExtentBuilder,
720}
721
722impl RenderSliverVariedExtentList {
723    /// Creates a sliver that contains multiple box children that have an explicit extent in the
724    /// main axis.
725    pub fn new(
726        app: &mut App,
727        child_manager: Rc<dyn RenderSliverBoxChildManager>,
728        item_extent_builder: ItemExtentBuilder,
729    ) -> RenderHandle<RenderSliverVariedExtentList> {
730        RenderHandle::new_sliver(
731            app,
732            RenderSliverVariedExtentList {
733                render_object: RenderObjectData::new(),
734                render_sliver: RenderSliverData::new(),
735                container: ContainerRenderObjectData::new(),
736                adaptor: RenderSliverMultiBoxAdaptorData::new(child_manager),
737                fixed_extent: RenderSliverFixedExtentBoxAdaptorData::new(),
738                item_extent_builder,
739            },
740        )
741    }
742
743    /// Sets [`RenderSliverFixedExtentBoxAdaptor::item_extent_builder`].
744    pub fn set_item_extent_builder(
745        self: RenderHandle<Self>,
746        app: &mut App,
747        value: ItemExtentBuilder,
748    ) {
749        if Rc::ptr_eq(&self.get(app).item_extent_builder, &value) {
750            return;
751        }
752        self.get_mut(app).item_extent_builder = value;
753        self.mark_needs_layout(app);
754    }
755}
756
757impl RenderSliverFixedExtentBoxAdaptor for RenderSliverVariedExtentList {
758    crate::render_sliver_fixed_extent_accessors!();
759
760    fn item_extent(self: RenderHandle<Self>, _app: &App) -> Option<f64> {
761        let _ = self;
762        None
763    }
764
765    fn item_extent_builder(self: RenderHandle<Self>, app: &App) -> Option<ItemExtentBuilder> {
766        Some(Rc::clone(&self.get(app).item_extent_builder))
767    }
768}
769
770fixed_extent_box_adaptor_leaf!(RenderSliverVariedExtentList);
771
772#[cfg(test)]
773mod tests {
774    use inset_embedder::Size;
775    use inset_foundation::AppCell;
776    use inset_gestures::HitTestResult;
777
778    use super::*;
779    use crate::sliver_multi_box_adaptor::test_support::TestChildManager;
780    use crate::sliver_multi_box_adaptor::viewport_test_support::ScrollHarness;
781
782    const VIEWPORT: Size = Size::new(100.0, 200.0);
783
784    fn fixed_list(
785        app: &mut App,
786        extents: Vec<f64>,
787        item_extent: f64,
788    ) -> (
789        RenderHandle<RenderSliverFixedExtentList>,
790        Rc<TestChildManager<RenderSliverFixedExtentList>>,
791        ScrollHarness,
792    ) {
793        let manager = TestChildManager::new(extents);
794        let list = RenderSliverFixedExtentList::new(app, manager.clone(), item_extent);
795        manager.attach(list);
796        let harness = ScrollHarness::new(app, list.as_sliver(), VIEWPORT);
797        (list, manager, harness)
798    }
799
800    /// `sliver_fixed_extent_list_test.dart`: only the children that fall in the viewport (plus
801    /// the cache area) are reified, and they are placed at multiples of the item extent.
802    #[test]
803    fn a_fixed_extent_list_reifies_only_the_visible_children() {
804        let cell = AppCell::new();
805        let mut app = cell.borrow_mut();
806        let (list, _, _harness) = fixed_list(&mut app, vec![50.0; 40], 50.0);
807
808        assert_eq!(
809            list.index_of(&app, list.first_child(&app).expect("built")),
810            0
811        );
812        assert_eq!(list.index_to_layout_offset(&app, 3), 150.0);
813        let geometry = list.geometry(&app);
814        assert_eq!(geometry.scroll_extent, 2000.0);
815        assert_eq!(geometry.paint_extent, 200.0);
816        assert!(geometry.has_visual_overflow);
817        // 200 pixels of viewport plus 250 of cache, at 50 pixels each, is far short of 40.
818        assert!(list.child_count(&app) < 40);
819    }
820
821    /// Scrolling collects the children that left the viewport and creates the ones that entered.
822    #[test]
823    fn scrolling_collects_and_creates_children() {
824        let cell = AppCell::new();
825        let mut app = cell.borrow_mut();
826        let (list, _, harness) = fixed_list(&mut app, vec![50.0; 40], 50.0);
827        assert_eq!(
828            list.index_of(&app, list.first_child(&app).expect("built")),
829            0
830        );
831
832        harness.scroll_to(&mut app, 600.0);
833
834        let first = list.first_child(&app).expect("children were created");
835        // 600 - 250 of leading cache = 350, which is index 7.
836        assert_eq!(list.index_of(&app, first), 7);
837        assert_eq!(
838            RenderSliver::child_scroll_offset(list, &app, first.as_object()),
839            Some(350.0)
840        );
841        assert_eq!(
842            RenderSliver::child_main_axis_position(list, &app, first.as_object()),
843            -250.0
844        );
845    }
846
847    /// A hit inside a reified child lands on the child and the sliver.
848    #[test]
849    fn a_fixed_extent_list_hit_tests_its_children() {
850        let cell = AppCell::new();
851        let mut app = cell.borrow_mut();
852        let (list, _, _harness) = fixed_list(&mut app, vec![50.0; 40], 50.0);
853
854        let mut result = HitTestResult::new();
855        let hit = list.hit_test(
856            &mut app,
857            &mut SliverHitTestResult::wrap(&mut result),
858            75.0,
859            20.0,
860        );
861
862        assert!(hit);
863        assert_eq!(result.path().len(), 2);
864    }
865
866    /// A child whose parent data asks to be kept alive moves to the keep-alive bucket instead of
867    /// being destroyed, and comes back out of it when it is needed again.
868    #[test]
869    fn a_kept_alive_child_survives_leaving_the_viewport() {
870        let cell = AppCell::new();
871        let mut app = cell.borrow_mut();
872        let (list, manager, harness) = fixed_list(&mut app, vec![50.0; 40], 50.0);
873        let first = list.first_child(&app).expect("children were created");
874        first
875            .as_object()
876            .parent_data_of_mut::<SliverMultiBoxAdaptorParentData>(&mut app)
877            .set_keep_alive(true);
878
879        harness.scroll_to(&mut app, 1000.0);
880
881        // The child is out of the child list but still attached, in the keep-alive bucket.
882        assert_eq!(list.keep_alive_children(&app), vec![first]);
883        assert!(
884            first
885                .as_object()
886                .parent_data_of::<SliverMultiBoxAdaptorParentData>(&app)
887                .kept_alive()
888        );
889        assert!(!list.paints_child(&app, first));
890        let created_before = manager.created.borrow().len();
891
892        harness.scroll_to(&mut app, 0.0);
893
894        // Index 0 came back from the bucket, so the manager was not asked to create it again.
895        assert_eq!(list.first_child(&app), Some(first));
896        assert!(list.keep_alive_children(&app).is_empty());
897        assert!(list.paints_child(&app, first));
898        assert!(
899            !manager.created.borrow()[created_before..].contains(&0),
900            "the kept-alive child was rebuilt"
901        );
902    }
903
904    /// A list shorter than the viewport reports underflow and its precise extent.
905    #[test]
906    fn a_short_list_reports_underflow() {
907        let cell = AppCell::new();
908        let mut app = cell.borrow_mut();
909        let (list, manager, _harness) = fixed_list(&mut app, vec![50.0; 2], 50.0);
910
911        assert_eq!(list.geometry(&app).scroll_extent, 100.0);
912        assert_eq!(list.geometry(&app).paint_extent, 100.0);
913        assert!(manager.did_underflow.get());
914    }
915
916    /// `RenderSliverVariedExtentList` asks its builder for each item's extent.
917    #[test]
918    fn a_varied_extent_list_uses_its_builder() {
919        let cell = AppCell::new();
920        let mut app = cell.borrow_mut();
921        let manager = TestChildManager::<RenderSliverVariedExtentList>::new(vec![40.0; 10]);
922        let builder: ItemExtentBuilder =
923            Rc::new(|index, _| Some(if index % 2 == 0 { 40.0 } else { 20.0 }));
924        let list = RenderSliverVariedExtentList::new(&mut app, manager.clone(), builder);
925        manager.attach(list);
926        let _harness = ScrollHarness::new(&mut app, list.as_sliver(), VIEWPORT);
927
928        // 40 + 20 + 40 + 20 = 120.
929        assert_eq!(list.index_to_layout_offset(&app, 4), 120.0);
930        assert_eq!(list.item_extent(&app), None);
931        assert_eq!(list.geometry(&app).paint_extent, 200.0);
932        // The manager estimates the total extent of its ten items.
933        assert_eq!(list.geometry(&app).scroll_extent, 400.0);
934    }
935
936    /// The minimum and maximum child indices bracket the visible window.
937    #[test]
938    fn the_child_index_range_brackets_the_scroll_offset() {
939        let cell = AppCell::new();
940        let mut app = cell.borrow_mut();
941        let (list, _, _harness) = fixed_list(&mut app, vec![50.0; 40], 50.0);
942        let constraints = list.constraints(&app);
943
944        assert_eq!(list.get_min_child_index_for_scroll_offset(&app, 120.0), 2);
945        assert_eq!(list.get_max_child_index_for_scroll_offset(&app, 120.0), 2);
946        assert_eq!(list.get_max_child_index_for_scroll_offset(&app, 0.0), 0);
947        assert_eq!(
948            list.compute_max_scroll_offset(&mut app, constraints),
949            2000.0
950        );
951    }
952}