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::builder()
350                        .scroll_extent(max)
351                        .max_paint_extent(max)
352                        .build(),
353                );
354                child_manager.did_finish_layout(app);
355                return;
356            }
357        }
358
359        let mut trailing_child_with_layout: Option<AnyRenderBox> = None;
360
361        let first_child = self.first_child(app).expect("added above");
362        let mut index = self.index_of(app, first_child) - 1;
363        while index >= first_index {
364            let child_constraints = self.child_constraints(app, index);
365            let Some(child) = self.insert_and_layout_leading_child(app, child_constraints, false)
366            else {
367                // Items before the previously first child are no longer present. Reset the scroll
368                // offset to offset all items prior and up to the missing item. Let the parent
369                // re-lay-out everything.
370                let correction = self.index_to_layout_offset(app, index);
371                self.set_geometry(
372                    app,
373                    SliverGeometry::builder()
374                        .scroll_offset_correction(correction)
375                        .build(),
376                );
377                return;
378            };
379            let layout_offset = self.index_to_layout_offset(app, index);
380            let child_parent_data = child
381                .as_object()
382                .parent_data_of_mut::<SliverMultiBoxAdaptorParentData>(app);
383            child_parent_data.set_layout_offset(Some(layout_offset));
384            debug_assert_eq!(child_parent_data.index, Some(index));
385            trailing_child_with_layout.get_or_insert(child);
386            index -= 1;
387        }
388
389        if trailing_child_with_layout.is_none() {
390            let first_child = self.first_child(app).expect("the child list is not empty");
391            let child_constraints = self.child_constraints(app, self.index_of(app, first_child));
392            first_child.layout(app, child_constraints, false);
393            let layout_offset = self.index_to_layout_offset(app, first_index);
394            first_child
395                .as_object()
396                .parent_data_of_mut::<SliverMultiBoxAdaptorParentData>(app)
397                .set_layout_offset(Some(layout_offset));
398            trailing_child_with_layout = Some(first_child);
399        }
400
401        let mut estimated_max_scroll_offset = f64::INFINITY;
402        let mut index = self.index_of(app, trailing_child_with_layout.expect("set above")) + 1;
403        while target_last_index.is_none_or(|last_index| index <= last_index) {
404            let after = trailing_child_with_layout.expect("set above");
405            let mut child = self.child_after(app, after);
406            if child.is_none_or(|current| self.index_of(app, current) != index) {
407                let child_constraints = self.child_constraints(app, index);
408                child = self.insert_and_layout_child(app, child_constraints, Some(after), false);
409                if child.is_none() {
410                    // We have run out of children.
411                    estimated_max_scroll_offset = self.index_to_layout_offset(app, index);
412                    break;
413                }
414            } else {
415                let child_constraints = self.child_constraints(app, index);
416                child
417                    .expect("checked")
418                    .layout(app, child_constraints, false);
419            }
420            let child = child.expect("set above");
421            trailing_child_with_layout = Some(child);
422            debug_assert_eq!(
423                child
424                    .as_object()
425                    .parent_data_of::<SliverMultiBoxAdaptorParentData>(app)
426                    .index,
427                Some(index)
428            );
429            let layout_offset = self.index_to_layout_offset(app, index);
430            child
431                .as_object()
432                .parent_data_of_mut::<SliverMultiBoxAdaptorParentData>(app)
433                .set_layout_offset(Some(layout_offset));
434            index += 1;
435        }
436
437        let last_child = self.last_child(app).expect("the child list is not empty");
438        let last_index = self.index_of(app, last_child);
439        let leading_scroll_offset = self.index_to_layout_offset(app, first_index);
440        let trailing_scroll_offset = self.index_to_layout_offset(app, last_index + 1);
441
442        debug_assert!(
443            first_index == 0
444                || RenderSliver::child_scroll_offset(
445                    self,
446                    app,
447                    self.first_child(app).expect("not empty").as_object()
448                )
449                .expect("laid out")
450                    - scroll_offset
451                    <= PRECISION_ERROR_TOLERANCE
452        );
453        debug_assert!(self.debug_assert_child_list_is_non_empty_and_contiguous(app));
454        debug_assert_eq!(
455            self.index_of(app, self.first_child(app).expect("not empty")),
456            first_index
457        );
458        debug_assert!(target_last_index.is_none_or(|target| last_index <= target));
459
460        estimated_max_scroll_offset =
461            estimated_max_scroll_offset.min(self.estimate_max_scroll_offset(
462                app,
463                constraints,
464                Some(first_index),
465                Some(last_index),
466                Some(leading_scroll_offset),
467                Some(trailing_scroll_offset),
468            ));
469
470        let paint_extent =
471            calculate_paint_offset(constraints, leading_scroll_offset, trailing_scroll_offset);
472        let cache_extent =
473            calculate_cache_offset(constraints, leading_scroll_offset, trailing_scroll_offset);
474
475        let target_end_scroll_offset_for_paint =
476            constraints.scroll_offset + constraints.remaining_paint_extent;
477        let target_last_index_for_paint =
478            target_end_scroll_offset_for_paint.is_finite().then(|| {
479                self.get_max_child_index_for_scroll_offset(app, target_end_scroll_offset_for_paint)
480            });
481
482        self.set_geometry(
483            app,
484            SliverGeometry::builder()
485                .scroll_extent(estimated_max_scroll_offset)
486                .paint_extent(paint_extent)
487                .cache_extent(cache_extent)
488                .max_paint_extent(estimated_max_scroll_offset)
489                // Conservative to avoid flickering away the clip during scroll.
490                .has_visual_overflow(
491                    target_last_index_for_paint.is_some_and(|target| last_index >= target)
492                        || constraints.scroll_offset > 0.0,
493                )
494                .build(),
495        );
496
497        // We may have started the layout while scrolled to the end, which would not expose a new
498        // child.
499        if estimated_max_scroll_offset == trailing_scroll_offset {
500            child_manager.set_did_underflow(app, true);
501        }
502        child_manager.did_finish_layout(app);
503    }
504}
505
506/// Declares the [`ContainerRenderObjectMixin`], [`RenderObject`] and [`RenderSliver`] impls that
507/// a [`RenderSliverFixedExtentBoxAdaptor`] leaf forwards to its base traits.
508macro_rules! fixed_extent_box_adaptor_leaf {
509    ($leaf:ty) => {
510        impl ContainerRenderObjectMixin for $leaf {
511            type ChildType = AnyRenderBox;
512            type ParentDataType = SliverMultiBoxAdaptorParentData;
513
514            fn container_data(
515                self: RenderHandle<Self>,
516                app: &App,
517            ) -> &ContainerRenderObjectData<AnyRenderBox> {
518                &self.get(app).container
519            }
520
521            fn container_data_mut(
522                self: RenderHandle<Self>,
523                app: &mut App,
524            ) -> &mut ContainerRenderObjectData<AnyRenderBox> {
525                &mut self.get_mut(app).container
526            }
527
528            fn insert(
529                self: RenderHandle<Self>,
530                app: &mut App,
531                child: AnyRenderBox,
532                after: Option<AnyRenderBox>,
533            ) {
534                RenderSliverMultiBoxAdaptor::insert(self, app, child, after)
535            }
536
537            fn move_child(
538                self: RenderHandle<Self>,
539                app: &mut App,
540                child: AnyRenderBox,
541                after: Option<AnyRenderBox>,
542            ) {
543                RenderSliverMultiBoxAdaptor::move_child(self, app, child, after)
544            }
545
546            fn remove(self: RenderHandle<Self>, app: &mut App, child: AnyRenderBox) {
547                RenderSliverMultiBoxAdaptor::remove(self, app, child)
548            }
549
550            fn remove_all(self: RenderHandle<Self>, app: &mut App) {
551                RenderSliverMultiBoxAdaptor::remove_all(self, app)
552            }
553        }
554
555        impl RenderSliverHelpers for $leaf {}
556
557        impl RenderSliverWithKeepAliveMixin for $leaf {}
558
559        impl RenderSliverMultiBoxAdaptor for $leaf {
560            crate::render_sliver_multi_box_adaptor_accessors!();
561
562            fn paint_extent_of(self: RenderHandle<Self>, app: &App, child: AnyRenderBox) -> f64 {
563                RenderSliverFixedExtentBoxAdaptor::paint_extent_of(self, app, child)
564            }
565        }
566
567        impl RenderObject for $leaf {
568            crate::render_object_accessors!();
569
570            fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
571                RenderSliverMultiBoxAdaptor::setup_parent_data(self, app, child);
572                RenderSliverWithKeepAliveMixin::setup_parent_data(self, app, child);
573            }
574
575            fn visit_children(
576                self: RenderHandle<Self>,
577                app: &App,
578                visitor: &mut dyn FnMut(AnyRenderObject),
579            ) {
580                RenderSliverMultiBoxAdaptor::visit_children(self, app, visitor)
581            }
582
583            fn did_attach(self: RenderHandle<Self>, app: &mut App, owner: Handle<PipelineOwner>) {
584                RenderSliverMultiBoxAdaptor::did_attach(self, app, owner)
585            }
586
587            fn did_detach(self: RenderHandle<Self>, app: &mut App) {
588                RenderSliverMultiBoxAdaptor::did_detach(self, app)
589            }
590
591            fn redepth_children(self: RenderHandle<Self>, app: &mut App) {
592                RenderSliverMultiBoxAdaptor::redepth_children(self, app)
593            }
594
595            fn apply_paint_transform(
596                self: RenderHandle<Self>,
597                app: &App,
598                child: AnyRenderObject,
599                transform: &mut Matrix4,
600            ) {
601                RenderSliverMultiBoxAdaptor::apply_paint_transform(self, app, child, transform)
602            }
603
604            fn paint(
605                self: RenderHandle<Self>,
606                app: &mut App,
607                context: &mut PaintingContext,
608                offset: Offset,
609            ) {
610                RenderSliverMultiBoxAdaptor::paint(self, app, context, offset)
611            }
612
613            fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
614                RenderSliverFixedExtentBoxAdaptor::perform_layout(self, app)
615            }
616        }
617
618        impl RenderSliver for $leaf {
619            crate::render_sliver_accessors!();
620
621            fn hit_test_children(
622                self: RenderHandle<Self>,
623                app: &mut App,
624                result: &mut SliverHitTestResult<'_>,
625                main_axis_position: f64,
626                cross_axis_position: f64,
627            ) -> bool {
628                RenderSliverMultiBoxAdaptor::hit_test_children(
629                    self,
630                    app,
631                    result,
632                    main_axis_position,
633                    cross_axis_position,
634                )
635            }
636
637            fn child_main_axis_position(
638                self: RenderHandle<Self>,
639                app: &App,
640                child: AnyRenderObject,
641            ) -> f64 {
642                RenderSliverMultiBoxAdaptor::child_main_axis_position(self, app, child)
643            }
644
645            fn child_scroll_offset(
646                self: RenderHandle<Self>,
647                app: &App,
648                child: AnyRenderObject,
649            ) -> Option<f64> {
650                RenderSliverMultiBoxAdaptor::child_scroll_offset(self, app, child)
651            }
652        }
653    };
654}
655
656/// A sliver that places multiple box children with the same main axis extent in a linear array.
657///
658/// [`RenderSliverFixedExtentList`] places its children in a linear array along the main axis
659/// starting at offset zero and without gaps. Each child is forced to have the
660/// [`item_extent`](Self::item_extent) in the main axis and the
661/// [`crate::SliverConstraints::cross_axis_extent`] in the cross axis.
662///
663/// [`RenderSliverFixedExtentList`] is more efficient than [`crate::RenderSliverList`] because it
664/// does not need to perform layout on its children to obtain their extent in the main axis.
665pub struct RenderSliverFixedExtentList {
666    render_object: RenderObjectData,
667    render_sliver: RenderSliverData,
668    container: ContainerRenderObjectData<AnyRenderBox>,
669    adaptor: RenderSliverMultiBoxAdaptorData,
670    fixed_extent: RenderSliverFixedExtentBoxAdaptorData,
671    item_extent: f64,
672}
673
674impl RenderSliverFixedExtentList {
675    /// Creates a sliver that contains multiple box children that have a given extent in the main
676    /// axis.
677    pub fn new(
678        app: &mut App,
679        child_manager: Rc<dyn RenderSliverBoxChildManager>,
680        item_extent: f64,
681    ) -> RenderHandle<RenderSliverFixedExtentList> {
682        RenderHandle::new_sliver(
683            app,
684            RenderSliverFixedExtentList {
685                render_object: RenderObjectData::new(),
686                render_sliver: RenderSliverData::new(),
687                container: ContainerRenderObjectData::new(),
688                adaptor: RenderSliverMultiBoxAdaptorData::new(child_manager),
689                fixed_extent: RenderSliverFixedExtentBoxAdaptorData::new(),
690                item_extent,
691            },
692        )
693    }
694
695    /// Sets [`RenderSliverFixedExtentBoxAdaptor::item_extent`].
696    pub fn set_item_extent(self: RenderHandle<Self>, app: &mut App, value: f64) {
697        if self.get(app).item_extent == value {
698            return;
699        }
700        self.get_mut(app).item_extent = value;
701        self.mark_needs_layout(app);
702    }
703}
704
705impl RenderSliverFixedExtentBoxAdaptor for RenderSliverFixedExtentList {
706    crate::render_sliver_fixed_extent_accessors!();
707
708    fn item_extent(self: RenderHandle<Self>, app: &App) -> Option<f64> {
709        Some(self.get(app).item_extent)
710    }
711}
712
713fixed_extent_box_adaptor_leaf!(RenderSliverFixedExtentList);
714
715/// A sliver that places multiple box children with the corresponding main axis extent in a linear
716/// array.
717pub struct RenderSliverVariedExtentList {
718    render_object: RenderObjectData,
719    render_sliver: RenderSliverData,
720    container: ContainerRenderObjectData<AnyRenderBox>,
721    adaptor: RenderSliverMultiBoxAdaptorData,
722    fixed_extent: RenderSliverFixedExtentBoxAdaptorData,
723    item_extent_builder: ItemExtentBuilder,
724}
725
726impl RenderSliverVariedExtentList {
727    /// Creates a sliver that contains multiple box children that have an explicit extent in the
728    /// main axis.
729    pub fn new(
730        app: &mut App,
731        child_manager: Rc<dyn RenderSliverBoxChildManager>,
732        item_extent_builder: ItemExtentBuilder,
733    ) -> RenderHandle<RenderSliverVariedExtentList> {
734        RenderHandle::new_sliver(
735            app,
736            RenderSliverVariedExtentList {
737                render_object: RenderObjectData::new(),
738                render_sliver: RenderSliverData::new(),
739                container: ContainerRenderObjectData::new(),
740                adaptor: RenderSliverMultiBoxAdaptorData::new(child_manager),
741                fixed_extent: RenderSliverFixedExtentBoxAdaptorData::new(),
742                item_extent_builder,
743            },
744        )
745    }
746
747    /// Sets [`RenderSliverFixedExtentBoxAdaptor::item_extent_builder`].
748    pub fn set_item_extent_builder(
749        self: RenderHandle<Self>,
750        app: &mut App,
751        value: ItemExtentBuilder,
752    ) {
753        if Rc::ptr_eq(&self.get(app).item_extent_builder, &value) {
754            return;
755        }
756        self.get_mut(app).item_extent_builder = value;
757        self.mark_needs_layout(app);
758    }
759}
760
761impl RenderSliverFixedExtentBoxAdaptor for RenderSliverVariedExtentList {
762    crate::render_sliver_fixed_extent_accessors!();
763
764    fn item_extent(self: RenderHandle<Self>, _app: &App) -> Option<f64> {
765        let _ = self;
766        None
767    }
768
769    fn item_extent_builder(self: RenderHandle<Self>, app: &App) -> Option<ItemExtentBuilder> {
770        Some(Rc::clone(&self.get(app).item_extent_builder))
771    }
772}
773
774fixed_extent_box_adaptor_leaf!(RenderSliverVariedExtentList);
775
776#[cfg(test)]
777mod tests {
778    use inset_embedder::Size;
779    use inset_foundation::AppCell;
780    use inset_gestures::HitTestResult;
781
782    use super::*;
783    use crate::sliver_multi_box_adaptor::test_support::TestChildManager;
784    use crate::sliver_multi_box_adaptor::viewport_test_support::ScrollHarness;
785
786    const VIEWPORT: Size = Size::new(100.0, 200.0);
787
788    fn fixed_list(
789        app: &mut App,
790        extents: Vec<f64>,
791        item_extent: f64,
792    ) -> (
793        RenderHandle<RenderSliverFixedExtentList>,
794        Rc<TestChildManager<RenderSliverFixedExtentList>>,
795        ScrollHarness,
796    ) {
797        let manager = TestChildManager::new(extents);
798        let list = RenderSliverFixedExtentList::new(app, manager.clone(), item_extent);
799        manager.attach(list);
800        let harness = ScrollHarness::new(app, list.as_sliver(), VIEWPORT);
801        (list, manager, harness)
802    }
803
804    /// `sliver_fixed_extent_list_test.dart`: only the children that fall in the viewport (plus
805    /// the cache area) are reified, and they are placed at multiples of the item extent.
806    #[test]
807    fn a_fixed_extent_list_reifies_only_the_visible_children() {
808        let cell = AppCell::new();
809        let mut app = cell.borrow_mut();
810        let (list, _, _harness) = fixed_list(&mut app, vec![50.0; 40], 50.0);
811
812        assert_eq!(
813            list.index_of(&app, list.first_child(&app).expect("built")),
814            0
815        );
816        assert_eq!(list.index_to_layout_offset(&app, 3), 150.0);
817        let geometry = list.geometry(&app);
818        assert_eq!(geometry.scroll_extent, 2000.0);
819        assert_eq!(geometry.paint_extent, 200.0);
820        assert!(geometry.has_visual_overflow);
821        // 200 pixels of viewport plus 250 of cache, at 50 pixels each, is far short of 40.
822        assert!(list.child_count(&app) < 40);
823    }
824
825    /// Scrolling collects the children that left the viewport and creates the ones that entered.
826    #[test]
827    fn scrolling_collects_and_creates_children() {
828        let cell = AppCell::new();
829        let mut app = cell.borrow_mut();
830        let (list, _, harness) = fixed_list(&mut app, vec![50.0; 40], 50.0);
831        assert_eq!(
832            list.index_of(&app, list.first_child(&app).expect("built")),
833            0
834        );
835
836        harness.scroll_to(&mut app, 600.0);
837
838        let first = list.first_child(&app).expect("children were created");
839        // 600 - 250 of leading cache = 350, which is index 7.
840        assert_eq!(list.index_of(&app, first), 7);
841        assert_eq!(
842            RenderSliver::child_scroll_offset(list, &app, first.as_object()),
843            Some(350.0)
844        );
845        assert_eq!(
846            RenderSliver::child_main_axis_position(list, &app, first.as_object()),
847            -250.0
848        );
849    }
850
851    /// A hit inside a reified child lands on the child and the sliver.
852    #[test]
853    fn a_fixed_extent_list_hit_tests_its_children() {
854        let cell = AppCell::new();
855        let mut app = cell.borrow_mut();
856        let (list, _, _harness) = fixed_list(&mut app, vec![50.0; 40], 50.0);
857
858        let mut result = HitTestResult::new();
859        let hit = list.hit_test(
860            &mut app,
861            &mut SliverHitTestResult::wrap(&mut result),
862            75.0,
863            20.0,
864        );
865
866        assert!(hit);
867        assert_eq!(result.path().len(), 2);
868    }
869
870    /// A child whose parent data asks to be kept alive moves to the keep-alive bucket instead of
871    /// being destroyed, and comes back out of it when it is needed again.
872    #[test]
873    fn a_kept_alive_child_survives_leaving_the_viewport() {
874        let cell = AppCell::new();
875        let mut app = cell.borrow_mut();
876        let (list, manager, harness) = fixed_list(&mut app, vec![50.0; 40], 50.0);
877        let first = list.first_child(&app).expect("children were created");
878        first
879            .as_object()
880            .parent_data_of_mut::<SliverMultiBoxAdaptorParentData>(&mut app)
881            .set_keep_alive(true);
882
883        harness.scroll_to(&mut app, 1000.0);
884
885        // The child is out of the child list but still attached, in the keep-alive bucket.
886        assert_eq!(list.keep_alive_children(&app), vec![first]);
887        assert!(
888            first
889                .as_object()
890                .parent_data_of::<SliverMultiBoxAdaptorParentData>(&app)
891                .kept_alive()
892        );
893        assert!(!list.paints_child(&app, first));
894        let created_before = manager.created.borrow().len();
895
896        harness.scroll_to(&mut app, 0.0);
897
898        // Index 0 came back from the bucket, so the manager was not asked to create it again.
899        assert_eq!(list.first_child(&app), Some(first));
900        assert!(list.keep_alive_children(&app).is_empty());
901        assert!(list.paints_child(&app, first));
902        assert!(
903            !manager.created.borrow()[created_before..].contains(&0),
904            "the kept-alive child was rebuilt"
905        );
906    }
907
908    /// A list shorter than the viewport reports underflow and its precise extent.
909    #[test]
910    fn a_short_list_reports_underflow() {
911        let cell = AppCell::new();
912        let mut app = cell.borrow_mut();
913        let (list, manager, _harness) = fixed_list(&mut app, vec![50.0; 2], 50.0);
914
915        assert_eq!(list.geometry(&app).scroll_extent, 100.0);
916        assert_eq!(list.geometry(&app).paint_extent, 100.0);
917        assert!(manager.did_underflow.get());
918    }
919
920    /// `RenderSliverVariedExtentList` asks its builder for each item's extent.
921    #[test]
922    fn a_varied_extent_list_uses_its_builder() {
923        let cell = AppCell::new();
924        let mut app = cell.borrow_mut();
925        let manager = TestChildManager::<RenderSliverVariedExtentList>::new(vec![40.0; 10]);
926        let builder: ItemExtentBuilder =
927            Rc::new(|index, _| Some(if index % 2 == 0 { 40.0 } else { 20.0 }));
928        let list = RenderSliverVariedExtentList::new(&mut app, manager.clone(), builder);
929        manager.attach(list);
930        let _harness = ScrollHarness::new(&mut app, list.as_sliver(), VIEWPORT);
931
932        // 40 + 20 + 40 + 20 = 120.
933        assert_eq!(list.index_to_layout_offset(&app, 4), 120.0);
934        assert_eq!(list.item_extent(&app), None);
935        assert_eq!(list.geometry(&app).paint_extent, 200.0);
936        // The manager estimates the total extent of its ten items.
937        assert_eq!(list.geometry(&app).scroll_extent, 400.0);
938    }
939
940    /// The minimum and maximum child indices bracket the visible window.
941    #[test]
942    fn the_child_index_range_brackets_the_scroll_offset() {
943        let cell = AppCell::new();
944        let mut app = cell.borrow_mut();
945        let (list, _, _harness) = fixed_list(&mut app, vec![50.0; 40], 50.0);
946        let constraints = list.constraints(&app);
947
948        assert_eq!(list.get_min_child_index_for_scroll_offset(&app, 120.0), 2);
949        assert_eq!(list.get_max_child_index_for_scroll_offset(&app, 120.0), 2);
950        assert_eq!(list.get_max_child_index_for_scroll_offset(&app, 0.0), 0);
951        assert_eq!(
952            list.compute_max_scroll_offset(&mut app, constraints),
953            2000.0
954        );
955    }
956}