Skip to main content

inset_rendering/
sliver_multi_box_adaptor.rs

1//! Flutter counterpart: `rendering/sliver_multi_box_adaptor.dart`.
2//!
3//! Semantics wait; see `PORTING.md`.
4
5use std::any::{Any, TypeId};
6use std::fmt::{self, Debug, Display};
7use std::rc::Rc;
8
9use indexmap::IndexMap;
10use inset_embedder::{Matrix4, Offset};
11use inset_foundation::{App, Handle};
12use inset_painting::{Axis, AxisDirection};
13
14use crate::box_::{AnyRenderBox, BoxConstraints, BoxHitTestResult};
15use crate::object::{
16    AnyRenderObject, ContainerParentData, ContainerParentDataMixin, ContainerRenderObjectBase,
17    ContainerRenderObjectMixin, ParentData, RenderHandle, RenderObject, zero_matrix,
18};
19use crate::painting_context::PaintingContext;
20use crate::pipeline_owner::PipelineOwner;
21use crate::sliver::{
22    RenderSliver, RenderSliverHelpers, SliverConstraints, SliverHitTestResult,
23    SliverLogicalParentData, apply_growth_direction_to_axis_direction,
24};
25
26/// A delegate used by [`RenderSliverMultiBoxAdaptor`] to manage its children.
27///
28/// [`RenderSliverMultiBoxAdaptor`] objects reify their children lazily to avoid spending
29/// resources on children that are not visible in the viewport. This delegate lets these objects
30/// create and remove children as well as estimate the total scroll offset extent occupied by the
31/// full child list.
32///
33/// The widget layer implements this; the render object holds it as an
34/// [`Rc<dyn RenderSliverBoxChildManager>`].
35pub trait RenderSliverBoxChildManager {
36    /// Called during layout when a new child is needed.
37    ///
38    /// The child should be inserted into the child list in the appropriate position, after the
39    /// `after` child (at the start of the list if `after` is `None`). Its index and scroll
40    /// offsets will automatically be set appropriately.
41    ///
42    /// The `index` argument gives the index of the child to show. It is possible for negative
43    /// indices to be requested. For example: if the user scrolls from child 0 to child 10, and
44    /// then those children get much smaller, and then the user scrolls back up again, this
45    /// method will eventually be asked to produce a child for index -1.
46    ///
47    /// If no child corresponds to `index`, then do nothing.
48    ///
49    /// Which child is indicated by index zero depends on the
50    /// [`crate::GrowthDirection`] specified in the `constraints` of the
51    /// [`RenderSliverMultiBoxAdaptor`].
52    ///
53    /// During a call to this method it is valid to remove other children from the
54    /// [`RenderSliverMultiBoxAdaptor`] object if they were not created during this frame and have
55    /// not yet been updated during this frame. It is not valid to add any other children to this
56    /// render object.
57    fn create_child(&self, app: &mut App, index: i32, after: Option<AnyRenderBox>);
58
59    /// Remove the given child from the child list.
60    ///
61    /// Called by [`RenderSliverMultiBoxAdaptor::collect_garbage`], which itself is called from
62    /// the adaptor's `perform_layout`.
63    ///
64    /// The index of the given child can be obtained using
65    /// [`RenderSliverMultiBoxAdaptor::index_of`].
66    fn remove_child(&self, app: &mut App, child: AnyRenderBox);
67
68    /// Called to estimate the total scrollable extents of this object.
69    ///
70    /// Must return the total distance from the start of the child with the earliest possible
71    /// index to the end of the child with the last possible index.
72    fn estimate_max_scroll_offset(
73        &self,
74        app: &App,
75        constraints: SliverConstraints,
76        first_index: Option<i32>,
77        last_index: Option<i32>,
78        leading_scroll_offset: Option<f64>,
79        trailing_scroll_offset: Option<f64>,
80    ) -> f64;
81
82    /// Called to obtain a precise measure of the total number of children.
83    ///
84    /// Must return the number that is one greater than the greatest `index` for which
85    /// [`create_child`](Self::create_child) will actually create a child.
86    ///
87    /// This is used when `create_child` cannot add a child for a positive `index`, to determine
88    /// the precise dimensions of the sliver. It will not be called if `create_child` is always
89    /// able to create a child (e.g. for an infinite list).
90    fn child_count(&self, app: &mut App) -> i32;
91
92    /// The best available estimate of [`child_count`](Self::child_count), or `None` if no
93    /// estimate is available.
94    fn estimated_child_count(&self, app: &App) -> Option<i32> {
95        let _ = app;
96        None
97    }
98
99    /// Called during [`RenderSliverMultiBoxAdaptor::insert`] or
100    /// [`RenderSliverMultiBoxAdaptor::move_child`].
101    ///
102    /// Implementors must ensure that the [`SliverMultiBoxAdaptorParentData::index`] field of the
103    /// child's parent data accurately reflects the child's index in the child list after this
104    /// function returns.
105    fn did_adopt_child(&self, app: &mut App, child: AnyRenderBox);
106
107    /// Called during layout to indicate whether this object provided insufficient children for
108    /// the [`RenderSliverMultiBoxAdaptor`] to fill the
109    /// [`SliverConstraints::remaining_paint_extent`].
110    ///
111    /// Typically called unconditionally at the start of layout with `false` and then later
112    /// called with `true` when the adaptor fails to create a child required to fill the
113    /// remaining paint extent.
114    fn set_did_underflow(&self, app: &mut App, value: bool);
115
116    /// Called at the beginning of layout to indicate that layout is about to occur.
117    fn did_start_layout(&self, app: &mut App) {
118        let _ = app;
119    }
120
121    /// Called at the end of layout to indicate that layout is now complete.
122    fn did_finish_layout(&self, app: &mut App) {
123        let _ = app;
124    }
125
126    /// In debug mode, asserts that this manager is not expecting any modifications to the
127    /// [`RenderSliverMultiBoxAdaptor`]'s child list.
128    ///
129    /// This function always returns true.
130    fn debug_assert_child_list_locked(&self, app: &App) -> bool {
131        let _ = app;
132        true
133    }
134}
135
136/// Parent data structure used by [`RenderSliverWithKeepAliveMixin`].
137///
138/// Flutter's `KeepAliveParentDataMixin`: the half a parent data embeds and answers from
139/// [`ParentData::provide`], which is how `KeepAlive` finds it.
140#[derive(Clone, Copy, Debug, Default)]
141pub struct KeepAliveParentData {
142    /// Whether to keep the child alive even when it is no longer visible.
143    pub keep_alive: bool,
144    kept_alive: bool,
145}
146
147impl KeepAliveParentData {
148    /// Parent data that does not keep its child alive.
149    pub const fn new() -> KeepAliveParentData {
150        KeepAliveParentData {
151            keep_alive: false,
152            kept_alive: false,
153        }
154    }
155
156    /// Whether the widget is currently being kept alive, i.e. has
157    /// [`keep_alive`](Self::keep_alive) set to true and is offscreen.
158    pub fn kept_alive(&self) -> bool {
159        self.kept_alive
160    }
161}
162
163/// This trait exists to dissociate `KeepAlive` from [`RenderSliverMultiBoxAdaptor`].
164///
165/// [`setup_parent_data`](Self::setup_parent_data) must be implemented to use a parent data type
166/// that embeds a [`KeepAliveParentData`].
167pub trait RenderSliverWithKeepAliveMixin: RenderSliver {
168    /// Alerts the developer that the child's parent data needs to embed a
169    /// [`KeepAliveParentData`].
170    fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
171        let _ = self;
172        debug_assert!(
173            child
174                .parent_data(app)
175                .is_some_and(|parent_data| parent_data.part::<KeepAliveParentData>().is_some())
176        );
177    }
178}
179
180/// Parent data structure used by [`RenderSliverMultiBoxAdaptor`].
181#[derive(Debug)]
182pub struct SliverMultiBoxAdaptorParentData {
183    sliver_logical: SliverLogicalParentData,
184    container: ContainerParentData<AnyRenderBox>,
185    keep_alive: KeepAliveParentData,
186    /// The index of this child according to the [`RenderSliverBoxChildManager`].
187    pub index: Option<i32>,
188}
189
190impl SliverMultiBoxAdaptorParentData {
191    /// Creates parent data with no index and no layout offset.
192    pub const fn new() -> SliverMultiBoxAdaptorParentData {
193        SliverMultiBoxAdaptorParentData {
194            sliver_logical: SliverLogicalParentData::new(),
195            container: ContainerParentData::new(),
196            keep_alive: KeepAliveParentData::new(),
197            index: None,
198        }
199    }
200
201    /// See [`SliverLogicalParentData::layout_offset`].
202    pub fn layout_offset(&self) -> Option<f64> {
203        self.sliver_logical.layout_offset
204    }
205
206    /// Sets [`layout_offset`](Self::layout_offset).
207    pub fn set_layout_offset(&mut self, value: Option<f64>) {
208        self.sliver_logical.layout_offset = value;
209    }
210
211    /// See [`KeepAliveParentData::keep_alive`].
212    pub fn keep_alive(&self) -> bool {
213        self.keep_alive.keep_alive
214    }
215
216    /// Sets [`keep_alive`](Self::keep_alive).
217    pub fn set_keep_alive(&mut self, value: bool) {
218        self.keep_alive.keep_alive = value;
219    }
220
221    /// See [`KeepAliveParentData::kept_alive`].
222    pub fn kept_alive(&self) -> bool {
223        self.keep_alive.kept_alive
224    }
225}
226
227impl Default for SliverMultiBoxAdaptorParentData {
228    fn default() -> SliverMultiBoxAdaptorParentData {
229        SliverMultiBoxAdaptorParentData::new()
230    }
231}
232
233impl ParentData for SliverMultiBoxAdaptorParentData {
234    fn detach(&mut self) {
235        ContainerParentDataMixin::detach(self);
236    }
237
238    fn provide(&self, id: TypeId) -> Option<&dyn Any> {
239        if id == TypeId::of::<SliverMultiBoxAdaptorParentData>() {
240            return Some(self);
241        }
242        if id == TypeId::of::<KeepAliveParentData>() {
243            return Some(&self.keep_alive);
244        }
245        self.sliver_logical.provide(id)
246    }
247
248    fn provide_mut(&mut self, id: TypeId) -> Option<&mut dyn Any> {
249        if id == TypeId::of::<SliverMultiBoxAdaptorParentData>() {
250            return Some(self);
251        }
252        if id == TypeId::of::<KeepAliveParentData>() {
253            return Some(&mut self.keep_alive);
254        }
255        self.sliver_logical.provide_mut(id)
256    }
257}
258
259impl ContainerParentDataMixin for SliverMultiBoxAdaptorParentData {
260    type ChildType = AnyRenderBox;
261
262    fn container_parent_data(&self) -> &ContainerParentData<AnyRenderBox> {
263        &self.container
264    }
265
266    fn container_parent_data_mut(&mut self) -> &mut ContainerParentData<AnyRenderBox> {
267        &mut self.container
268    }
269}
270
271impl Display for SliverMultiBoxAdaptorParentData {
272    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273        write!(f, "index={:?}; ", self.index)?;
274        if self.keep_alive() {
275            f.write_str("keepAlive; ")?;
276        }
277        Display::fmt(&self.sliver_logical, f)
278    }
279}
280
281/// Gives `child`, whose parent data `drop_child` cleared, `sliver`'s kind of parent data again
282/// holding `saved`, marked kept alive.
283///
284/// Dart re-assigns the parent data object it saved before `dropChild` nulled it. Here the object
285/// is gone, so the sliver's own [`RenderObject::setup_parent_data`] makes a new one (a grid's,
286/// for a grid) and the saved fields are written back. A subclass's own fields, such as a grid
287/// tile's cross-axis offset, start empty: a kept-alive child is neither painted nor hit, and it
288/// is laid out again before it shows.
289fn restore_parent_data<T: RenderSliverMultiBoxAdaptor>(
290    sliver: RenderHandle<T>,
291    app: &mut App,
292    child: AnyRenderBox,
293    saved: KeptSlot,
294) {
295    RenderObject::setup_parent_data(sliver, app, child.as_object());
296    let restored = child
297        .as_object()
298        .parent_data_of_mut::<SliverMultiBoxAdaptorParentData>(app);
299    restored.set_layout_offset(saved.layout_offset);
300    restored.set_keep_alive(saved.keep_alive);
301    restored.index = saved.index;
302    restored.keep_alive.kept_alive = true;
303}
304
305/// The fields of a child's [`SliverMultiBoxAdaptorParentData`] that a move into or out of the
306/// keep-alive bucket keeps.
307#[derive(Clone, Copy)]
308struct KeptSlot {
309    layout_offset: Option<f64>,
310    keep_alive: bool,
311    index: Option<i32>,
312}
313
314impl KeptSlot {
315    fn of(parent_data: &SliverMultiBoxAdaptorParentData) -> KeptSlot {
316        KeptSlot {
317            layout_offset: parent_data.layout_offset(),
318            keep_alive: parent_data.keep_alive(),
319            index: parent_data.index,
320        }
321    }
322}
323
324/// Flutter's `RenderSliverMultiBoxAdaptor` fields.
325pub struct RenderSliverMultiBoxAdaptorData {
326    child_manager: Rc<dyn RenderSliverBoxChildManager>,
327    /// The nodes being kept alive despite not being visible.
328    keep_alive_bucket: IndexMap<i32, AnyRenderBox>,
329    debug_dangling_keep_alives: Vec<AnyRenderBox>,
330    debug_child_integrity_enabled: bool,
331}
332
333impl RenderSliverMultiBoxAdaptorData {
334    /// Creates the state of a sliver with multiple box children.
335    pub fn new(
336        child_manager: Rc<dyn RenderSliverBoxChildManager>,
337    ) -> RenderSliverMultiBoxAdaptorData {
338        RenderSliverMultiBoxAdaptorData {
339            child_manager,
340            keep_alive_bucket: IndexMap::new(),
341            debug_dangling_keep_alives: Vec::new(),
342            debug_child_integrity_enabled: true,
343        }
344    }
345}
346
347/// Implements [`RenderSliverMultiBoxAdaptor`] field accessors for an `adaptor` field.
348#[macro_export]
349macro_rules! render_sliver_multi_box_adaptor_accessors {
350    () => {
351        fn adaptor_data(
352            self: $crate::RenderHandle<Self>,
353            app: &::inset_foundation::App,
354        ) -> &$crate::RenderSliverMultiBoxAdaptorData {
355            &self.get(app).adaptor
356        }
357        fn adaptor_data_mut(
358            self: $crate::RenderHandle<Self>,
359            app: &mut ::inset_foundation::App,
360        ) -> &mut $crate::RenderSliverMultiBoxAdaptorData {
361            &mut self.get_mut(app).adaptor
362        }
363    };
364}
365
366/// A sliver with multiple box children.
367///
368/// [`RenderSliverMultiBoxAdaptor`] is a base class for slivers that have multiple box children.
369/// The children are managed by a [`RenderSliverBoxChildManager`], which lets implementors create
370/// children lazily during layout. Typically implementors will create only those children that are
371/// actually needed to fill the [`SliverConstraints::remaining_paint_extent`].
372///
373/// The contract for adding and removing children from this render object is more strict than for
374/// normal render objects:
375///
376/// * Children can be removed except during a layout pass if they have already been laid out
377///   during that layout pass.
378/// * Children cannot be added except during a call to
379///   [`child_manager`](Self::child_manager), and then only if there is no child corresponding to
380///   that index (or the child corresponding to that index was first removed).
381///
382/// See also:
383///
384///  * [`crate::RenderSliverToBoxAdapter`], which has a single box child.
385///  * [`crate::RenderSliverList`], which places its children in a linear array.
386///  * [`crate::RenderSliverFixedExtentList`], which places its children in a linear array with a
387///    fixed extent in the main axis.
388pub trait RenderSliverMultiBoxAdaptor:
389    RenderSliver
390    + RenderSliverHelpers
391    + RenderSliverWithKeepAliveMixin
392    + ContainerRenderObjectMixin<
393        ChildType = AnyRenderBox,
394        ParentDataType = SliverMultiBoxAdaptorParentData,
395    >
396{
397    /// Mixin field access.
398    fn adaptor_data(self: RenderHandle<Self>, app: &App) -> &RenderSliverMultiBoxAdaptorData;
399
400    /// See [`adaptor_data`](Self::adaptor_data).
401    fn adaptor_data_mut(
402        self: RenderHandle<Self>,
403        app: &mut App,
404    ) -> &mut RenderSliverMultiBoxAdaptorData;
405
406    /// The delegate that manages the children of this object.
407    ///
408    /// Rather than having a concrete list of children, a [`RenderSliverMultiBoxAdaptor`] uses a
409    /// [`RenderSliverBoxChildManager`] to create children during layout in order to fill the
410    /// [`SliverConstraints::remaining_paint_extent`].
411    fn child_manager(self: RenderHandle<Self>, app: &App) -> Rc<dyn RenderSliverBoxChildManager> {
412        Rc::clone(&self.adaptor_data(app).child_manager)
413    }
414
415    /// The body of Flutter's `setupParentData` override.
416    fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
417        if !child.parent_data_is::<SliverMultiBoxAdaptorParentData>(app) {
418            child.set_parent_data(app, SliverMultiBoxAdaptorParentData::new());
419        }
420    }
421
422    /// Whether the integrity check is enabled.
423    ///
424    /// The integrity check consists of:
425    ///
426    /// 1. Verify that the children index in the child list is in ascending order.
427    /// 2. Verify that there is no dangling keep-alive child as the result of
428    ///    [`move_child`](Self::move_child).
429    fn debug_child_integrity_enabled(self: RenderHandle<Self>, app: &App) -> bool {
430        self.adaptor_data(app).debug_child_integrity_enabled
431    }
432
433    /// Sets [`debug_child_integrity_enabled`](Self::debug_child_integrity_enabled), immediately
434    /// performing an integrity check.
435    fn set_debug_child_integrity_enabled(self: RenderHandle<Self>, app: &mut App, enabled: bool) {
436        if !cfg!(debug_assertions) {
437            return;
438        }
439        self.adaptor_data_mut(app).debug_child_integrity_enabled = enabled;
440        debug_assert!(self.debug_verify_child_order(app));
441        debug_assert!(
442            !enabled || self.adaptor_data(app).debug_dangling_keep_alives.is_empty(),
443            "a keep-alive child was left dangling by move_child"
444        );
445    }
446
447    /// Flutter's `_debugAssertChildListLocked`.
448    fn debug_assert_child_list_locked(self: RenderHandle<Self>, app: &App) -> bool {
449        self.child_manager(app).debug_assert_child_list_locked(app)
450    }
451
452    /// Verify that the child list index is in strictly increasing order.
453    ///
454    /// This has no effect in release builds.
455    fn debug_verify_child_order(self: RenderHandle<Self>, app: &App) -> bool {
456        if self.debug_child_integrity_enabled(app) {
457            let mut child = self.first_child(app);
458            while let Some(current) = child {
459                let index = self.index_of(app, current);
460                child = self.child_after(app, current);
461                debug_assert!(child.is_none_or(|next| self.index_of(app, next) > index));
462            }
463        }
464        true
465    }
466
467    /// The body of Flutter's `insert` override.
468    fn insert(
469        self: RenderHandle<Self>,
470        app: &mut App,
471        child: AnyRenderBox,
472        after: Option<AnyRenderBox>,
473    ) {
474        debug_assert!(
475            !self
476                .adaptor_data(app)
477                .keep_alive_bucket
478                .values()
479                .any(|kept| *kept == child)
480        );
481        ContainerRenderObjectBase::insert(self, app, child, after);
482        self.did_adopt_child(app, child);
483        debug_assert!(self.first_child(app).is_some());
484        debug_assert!(self.debug_verify_child_order(app));
485    }
486
487    /// The body of Flutter's `adoptChild` override, run after a child joins the child list.
488    ///
489    /// Flutter overrides `RenderObject.adoptChild`; here the tree operation is not virtual, so
490    /// the two callers ([`insert`](Self::insert) and Dart's `_destroyOrCacheChild`, which takes
491    /// `super.adoptChild`) decide whether it runs.
492    fn did_adopt_child(self: RenderHandle<Self>, app: &mut App, child: AnyRenderBox) {
493        let kept_alive = child
494            .as_object()
495            .parent_data_of::<SliverMultiBoxAdaptorParentData>(app)
496            .kept_alive();
497        if !kept_alive {
498            self.child_manager(app).did_adopt_child(app, child);
499        }
500    }
501
502    /// The body of Flutter's `move` override.
503    ///
504    /// There are two scenarios:
505    ///
506    /// 1. The child is not kept alive. The child is in the child list maintained by
507    ///    [`ContainerRenderObjectMixin`]. We can call the inherited body and update the parent
508    ///    data with the new slot.
509    ///
510    /// 2. The child is kept alive. In this case, the child is no longer in the child list but
511    ///    might be stored in the keep-alive bucket. We need to update the location of the child
512    ///    in the bucket.
513    fn move_child(
514        self: RenderHandle<Self>,
515        app: &mut App,
516        child: AnyRenderBox,
517        after: Option<AnyRenderBox>,
518    ) {
519        let child_parent_data = child
520            .as_object()
521            .parent_data_of::<SliverMultiBoxAdaptorParentData>(app);
522        let (kept_alive, index) = (child_parent_data.kept_alive(), child_parent_data.index);
523        if !kept_alive {
524            ContainerRenderObjectBase::move_child(self, app, child, after);
525            // updates the slot in the parent data
526            self.child_manager(app).did_adopt_child(app, child);
527            // Its slot may change even if the inherited body does not change the position. In
528            // this case, we still want to mark as needs layout.
529            self.mark_needs_layout(app);
530            return;
531        }
532        // If the child in the bucket is not the current child, that means someone has already
533        // moved and replaced the current child, and we cannot remove this child.
534        if let Some(index) = index
535            && self.adaptor_data(app).keep_alive_bucket.get(&index) == Some(&child)
536        {
537            self.adaptor_data_mut(app)
538                .keep_alive_bucket
539                .shift_remove(&index);
540        }
541        if cfg!(debug_assertions) {
542            self.adaptor_data_mut(app)
543                .debug_dangling_keep_alives
544                .retain(|dangling| *dangling != child);
545        }
546        // Update the slot and reinsert back to the keep-alive bucket in the new slot.
547        self.child_manager(app).did_adopt_child(app, child);
548        // If there is an existing child in the new slot, that means that child will be moved to
549        // another index. In other cases, the existing child should have been removed by
550        // update_child. Thus, it is ok to overwrite it.
551        let new_index = child
552            .as_object()
553            .parent_data_of::<SliverMultiBoxAdaptorParentData>(app)
554            .index
555            .expect("did_adopt_child sets the index");
556        if cfg!(debug_assertions)
557            && let Some(existing) = self
558                .adaptor_data(app)
559                .keep_alive_bucket
560                .get(&new_index)
561                .copied()
562        {
563            self.adaptor_data_mut(app)
564                .debug_dangling_keep_alives
565                .push(existing);
566        }
567        self.adaptor_data_mut(app)
568            .keep_alive_bucket
569            .insert(new_index, child);
570    }
571
572    /// The body of Flutter's `remove` override.
573    fn remove(self: RenderHandle<Self>, app: &mut App, child: AnyRenderBox) {
574        let child_parent_data = child
575            .as_object()
576            .parent_data_of::<SliverMultiBoxAdaptorParentData>(app);
577        let (kept_alive, index) = (child_parent_data.kept_alive(), child_parent_data.index);
578        if !kept_alive {
579            ContainerRenderObjectBase::remove(self, app, child);
580            return;
581        }
582        let index = index.expect("a kept-alive child has an index");
583        debug_assert_eq!(
584            self.adaptor_data(app).keep_alive_bucket.get(&index),
585            Some(&child)
586        );
587        if cfg!(debug_assertions) {
588            self.adaptor_data_mut(app)
589                .debug_dangling_keep_alives
590                .retain(|dangling| *dangling != child);
591        }
592        self.adaptor_data_mut(app)
593            .keep_alive_bucket
594            .shift_remove(&index);
595        self.drop_child(app, child.as_object());
596    }
597
598    /// The body of Flutter's `removeAll` override.
599    fn remove_all(self: RenderHandle<Self>, app: &mut App) {
600        ContainerRenderObjectBase::remove_all(self, app);
601        for child in self.keep_alive_children(app) {
602            self.drop_child(app, child.as_object());
603        }
604        self.adaptor_data_mut(app).keep_alive_bucket.clear();
605    }
606
607    /// The children currently held in the keep-alive bucket, in insertion order.
608    fn keep_alive_children(self: RenderHandle<Self>, app: &App) -> Vec<AnyRenderBox> {
609        self.adaptor_data(app)
610            .keep_alive_bucket
611            .values()
612            .copied()
613            .collect()
614    }
615
616    /// Flutter's `_createOrObtainChild`.
617    fn create_or_obtain_child(
618        self: RenderHandle<Self>,
619        app: &mut App,
620        index: i32,
621        after: Option<AnyRenderBox>,
622    ) {
623        self.as_object().invoke_layout_callback(app, |app| {
624            let cached = self
625                .adaptor_data(app)
626                .keep_alive_bucket
627                .get(&index)
628                .copied();
629            let Some(child) = cached else {
630                self.child_manager(app).create_child(app, index, after);
631                return;
632            };
633            self.adaptor_data_mut(app)
634                .keep_alive_bucket
635                .shift_remove(&index);
636            let child_parent_data = child
637                .as_object()
638                .parent_data_of::<SliverMultiBoxAdaptorParentData>(app);
639            debug_assert!(child_parent_data.kept_alive());
640            let saved = KeptSlot::of(child_parent_data);
641            self.drop_child(app, child.as_object());
642            restore_parent_data(self, app, child, saved);
643            RenderSliverMultiBoxAdaptor::insert(self, app, child, after);
644            child
645                .as_object()
646                .parent_data_of_mut::<SliverMultiBoxAdaptorParentData>(app)
647                .keep_alive
648                .kept_alive = false;
649        });
650    }
651
652    /// Flutter's `_destroyOrCacheChild`.
653    fn destroy_or_cache_child(self: RenderHandle<Self>, app: &mut App, child: AnyRenderBox) {
654        let child_parent_data = child
655            .as_object()
656            .parent_data_of::<SliverMultiBoxAdaptorParentData>(app);
657        if !child_parent_data.keep_alive() {
658            debug_assert_eq!(child.as_object().parent(app), Some(self.as_object()));
659            self.child_manager(app).remove_child(app, child);
660            debug_assert!(child.as_object().parent(app).is_none());
661            return;
662        }
663        debug_assert!(!child_parent_data.kept_alive());
664        let saved = KeptSlot::of(child_parent_data);
665        let index = saved.index.expect("a cached child has an index");
666        RenderSliverMultiBoxAdaptor::remove(self, app, child);
667        self.adaptor_data_mut(app)
668            .keep_alive_bucket
669            .insert(index, child);
670        restore_parent_data(self, app, child, saved);
671        // Dart's `super.adoptChild`: the child manager is not told, because the child keeps its
672        // slot.
673        self.as_object().adopt_child(app, child.as_object());
674    }
675
676    /// The body of Flutter's `attach` override: attaches the kept-alive children too.
677    fn did_attach(self: RenderHandle<Self>, app: &mut App, owner: Handle<PipelineOwner>) {
678        ContainerRenderObjectMixin::did_attach(self, app, owner);
679        for child in self.keep_alive_children(app) {
680            child.as_object().attach(app, owner);
681        }
682    }
683
684    /// The body of Flutter's `detach` override: detaches the kept-alive children too.
685    fn did_detach(self: RenderHandle<Self>, app: &mut App) {
686        ContainerRenderObjectMixin::did_detach(self, app);
687        for child in self.keep_alive_children(app) {
688            child.as_object().detach(app);
689        }
690    }
691
692    /// The body of Flutter's `redepthChildren` override.
693    fn redepth_children(self: RenderHandle<Self>, app: &mut App) {
694        ContainerRenderObjectMixin::redepth_children(self, app);
695        for child in self.keep_alive_children(app) {
696            self.as_object().redepth_child(app, child.as_object());
697        }
698    }
699
700    /// The body of Flutter's `visitChildren` override: visits the kept-alive children too.
701    fn visit_children(
702        self: RenderHandle<Self>,
703        app: &App,
704        visitor: &mut dyn FnMut(AnyRenderObject),
705    ) {
706        ContainerRenderObjectMixin::visit_children(self, app, visitor);
707        for child in self.keep_alive_children(app) {
708            visitor(child.as_object());
709        }
710    }
711
712    /// Called during layout to create and add the child with the given index and scroll offset.
713    ///
714    /// Calls [`RenderSliverBoxChildManager::create_child`] to actually create and add the child
715    /// if necessary. The child may instead be obtained from a cache; see
716    /// [`KeepAliveParentData::keep_alive`].
717    ///
718    /// Returns `false` if there was no cached child and `create_child` did not add any child,
719    /// otherwise returns `true`.
720    ///
721    /// Does not lay out the new child.
722    fn add_initial_child(
723        self: RenderHandle<Self>,
724        app: &mut App,
725        index: i32,
726        layout_offset: f64,
727    ) -> bool {
728        debug_assert!(self.debug_assert_child_list_locked(app));
729        debug_assert!(self.first_child(app).is_none());
730        self.create_or_obtain_child(app, index, None);
731        if let Some(first_child) = self.first_child(app) {
732            debug_assert_eq!(Some(first_child), self.last_child(app));
733            debug_assert_eq!(self.index_of(app, first_child), index);
734            first_child
735                .as_object()
736                .parent_data_of_mut::<SliverMultiBoxAdaptorParentData>(app)
737                .set_layout_offset(Some(layout_offset));
738            return true;
739        }
740        self.child_manager(app).set_did_underflow(app, true);
741        false
742    }
743
744    /// Called during layout to create, add, and lay out the child before
745    /// [`ContainerRenderObjectMixin::first_child`].
746    ///
747    /// Returns the new child or `None` if no child was obtained.
748    ///
749    /// The child that was previously the first child, as well as any subsequent children, may be
750    /// removed by this call if they have not yet been laid out during this layout pass.
751    fn insert_and_layout_leading_child(
752        self: RenderHandle<Self>,
753        app: &mut App,
754        child_constraints: BoxConstraints,
755        parent_uses_size: bool,
756    ) -> Option<AnyRenderBox> {
757        debug_assert!(self.debug_assert_child_list_locked(app));
758        let first_child = self.first_child(app).expect("there is a leading child");
759        let index = self.index_of(app, first_child) - 1;
760        self.create_or_obtain_child(app, index, None);
761        let first_child = self.first_child(app).expect("the child list is not empty");
762        if self.index_of(app, first_child) == index {
763            first_child.layout(app, child_constraints, parent_uses_size);
764            return Some(first_child);
765        }
766        self.child_manager(app).set_did_underflow(app, true);
767        None
768    }
769
770    /// Called during layout to create, add, and lay out the child after the given child.
771    ///
772    /// Returns the new child. It is the responsibility of the caller to configure the child's
773    /// scroll offset.
774    ///
775    /// Children after the `after` child may be removed in the process. Only the new child may be
776    /// added.
777    fn insert_and_layout_child(
778        self: RenderHandle<Self>,
779        app: &mut App,
780        child_constraints: BoxConstraints,
781        after: Option<AnyRenderBox>,
782        parent_uses_size: bool,
783    ) -> Option<AnyRenderBox> {
784        debug_assert!(self.debug_assert_child_list_locked(app));
785        let after = after.expect("insert_and_layout_child needs a preceding child");
786        let index = self.index_of(app, after) + 1;
787        self.create_or_obtain_child(app, index, Some(after));
788        let child = self.child_after(app, after);
789        if let Some(child) = child
790            && self.index_of(app, child) == index
791        {
792            child.layout(app, child_constraints, parent_uses_size);
793            return Some(child);
794        }
795        self.child_manager(app).set_did_underflow(app, true);
796        None
797    }
798
799    /// Returns the number of children preceding the `first_index` that need to be garbage
800    /// collected.
801    fn calculate_leading_garbage(self: RenderHandle<Self>, app: &App, first_index: i32) -> usize {
802        let mut walker = self.first_child(app);
803        let mut leading_garbage = 0;
804        while let Some(current) = walker {
805            if self.index_of(app, current) >= first_index {
806                break;
807            }
808            leading_garbage += 1;
809            walker = self.child_after(app, current);
810        }
811        leading_garbage
812    }
813
814    /// Returns the number of children following the `last_index` that need to be garbage
815    /// collected.
816    fn calculate_trailing_garbage(self: RenderHandle<Self>, app: &App, last_index: i32) -> usize {
817        let mut walker = self.last_child(app);
818        let mut trailing_garbage = 0;
819        while let Some(current) = walker {
820            if self.index_of(app, current) <= last_index {
821                break;
822            }
823            trailing_garbage += 1;
824            walker = self.child_before(app, current);
825        }
826        trailing_garbage
827    }
828
829    /// Called after layout with the number of children that can be garbage collected at the head
830    /// and tail of the child list.
831    ///
832    /// Children whose [`KeepAliveParentData::keep_alive`] property is set to true will be
833    /// removed to a cache instead of being dropped.
834    ///
835    /// This method also collects any children that were previously kept alive but are now no
836    /// longer necessary. As such, it should be called every time `perform_layout` is run, even if
837    /// the arguments are both zero.
838    fn collect_garbage(
839        self: RenderHandle<Self>,
840        app: &mut App,
841        leading_garbage: usize,
842        trailing_garbage: usize,
843    ) {
844        debug_assert!(self.debug_assert_child_list_locked(app));
845        debug_assert!(self.child_count(app) >= leading_garbage + trailing_garbage);
846        self.as_object().invoke_layout_callback(app, |app| {
847            for _ in 0..leading_garbage {
848                let first_child = self.first_child(app).expect("counted above");
849                self.destroy_or_cache_child(app, first_child);
850            }
851            for _ in 0..trailing_garbage {
852                let last_child = self.last_child(app).expect("counted above");
853                self.destroy_or_cache_child(app, last_child);
854            }
855            // Ask the child manager to remove the children that are no longer being kept alive.
856            // (This should cause the keep-alive bucket to change, so we have to prepare our list
857            // ahead of time.)
858            let expired: Vec<AnyRenderBox> = self
859                .keep_alive_children(app)
860                .into_iter()
861                .filter(|child| {
862                    !child
863                        .as_object()
864                        .parent_data_of::<SliverMultiBoxAdaptorParentData>(app)
865                        .keep_alive()
866                })
867                .collect();
868            let child_manager = self.child_manager(app);
869            for child in expired {
870                child_manager.remove_child(app, child);
871            }
872            debug_assert!(self.keep_alive_children(app).iter().all(|child| {
873                child
874                    .as_object()
875                    .parent_data_of::<SliverMultiBoxAdaptorParentData>(app)
876                    .keep_alive()
877            }));
878        });
879    }
880
881    /// Returns the index of the given child, as given by the
882    /// [`SliverMultiBoxAdaptorParentData::index`] field of the child's parent data.
883    fn index_of(self: RenderHandle<Self>, app: &App, child: AnyRenderBox) -> i32 {
884        child
885            .as_object()
886            .parent_data_of::<SliverMultiBoxAdaptorParentData>(app)
887            .index
888            .expect("a reified child has an index")
889    }
890
891    /// Returns the dimension of the given child in the main axis, as given by the child's size.
892    ///
893    /// This is only valid after layout.
894    fn paint_extent_of(self: RenderHandle<Self>, app: &App, child: AnyRenderBox) -> f64 {
895        debug_assert!(child.has_size(app));
896        match self.constraints(app).axis() {
897            Axis::Horizontal => child.size(app).width(),
898            Axis::Vertical => child.size(app).height(),
899        }
900    }
901
902    /// The body of Flutter's `hitTestChildren` override.
903    fn hit_test_children(
904        self: RenderHandle<Self>,
905        app: &mut App,
906        result: &mut SliverHitTestResult<'_>,
907        main_axis_position: f64,
908        cross_axis_position: f64,
909    ) -> bool {
910        let mut child = self.last_child(app);
911        while let Some(current) = child {
912            let previous = self.child_before(app, current);
913            if self.hit_test_box_child(
914                app,
915                &mut BoxHitTestResult::wrap(result),
916                current,
917                main_axis_position,
918                cross_axis_position,
919            ) {
920                return true;
921            }
922            child = previous;
923        }
924        false
925    }
926
927    /// The body of Flutter's `childMainAxisPosition` override.
928    fn child_main_axis_position(
929        self: RenderHandle<Self>,
930        app: &App,
931        child: AnyRenderObject,
932    ) -> f64 {
933        RenderSliverMultiBoxAdaptor::child_scroll_offset(self, app, child)
934            .expect("a laid-out child has a scroll offset")
935            - self.constraints(app).scroll_offset
936    }
937
938    /// The body of Flutter's `childScrollOffset` override.
939    fn child_scroll_offset(
940        self: RenderHandle<Self>,
941        app: &App,
942        child: AnyRenderObject,
943    ) -> Option<f64> {
944        debug_assert_eq!(child.parent(app), Some(self.as_object()));
945        child
946            .parent_data_of::<SliverMultiBoxAdaptorParentData>(app)
947            .layout_offset()
948    }
949
950    /// Whether the given child is painted, i.e. it is reified and not kept alive off-screen.
951    ///
952    /// Flutter's `paintsChild` override; `RenderObject.paintsChild` is not a virtual here (see
953    /// `PORTING.md`).
954    fn paints_child(self: RenderHandle<Self>, app: &App, child: AnyRenderBox) -> bool {
955        let child = child.as_object();
956        let index = child
957            .parent_data_is::<SliverMultiBoxAdaptorParentData>(app)
958            .then(|| {
959                child
960                    .parent_data_of::<SliverMultiBoxAdaptorParentData>(app)
961                    .index
962            })
963            .flatten();
964        match index {
965            None => false,
966            Some(index) => !self
967                .adaptor_data(app)
968                .keep_alive_bucket
969                .contains_key(&index),
970        }
971    }
972
973    /// The body of Flutter's `applyPaintTransform` override.
974    fn apply_paint_transform(
975        self: RenderHandle<Self>,
976        app: &App,
977        child: AnyRenderObject,
978        transform: &mut Matrix4,
979    ) {
980        let child = child.as_box().expect("the children are boxes");
981        if self.paints_child(app, child) {
982            self.apply_paint_transform_for_box_child(app, child, transform);
983        } else {
984            // This can happen if some child asks for the global transform even though they are
985            // not getting painted. In that case, the transform is set to zero since
986            // apply_paint_transform_for_box_child would end up throwing due to the child not
987            // being configured correctly for applying a transform. There's no assert here because
988            // asking for the paint transform is a valid thing to do even if a child would not be
989            // painted, but there is no meaningful non-zero matrix to use in this case.
990            *transform = zero_matrix();
991        }
992    }
993
994    /// The body of Flutter's `paint` override.
995    fn paint(
996        self: RenderHandle<Self>,
997        app: &mut App,
998        context: &mut PaintingContext,
999        offset: Offset,
1000    ) {
1001        if self.first_child(app).is_none() {
1002            return;
1003        }
1004        // offset is to the top-left corner, regardless of our axis direction.
1005        // origin_offset gives us the delta from the real origin to the origin in the axis
1006        // direction.
1007        let constraints = self.constraints(app);
1008        let paint_extent = self.geometry(app).paint_extent;
1009        let (main_axis_unit, cross_axis_unit, origin_offset, add_extent) =
1010            match apply_growth_direction_to_axis_direction(
1011                constraints.axis_direction,
1012                constraints.growth_direction,
1013            ) {
1014                AxisDirection::Up => (
1015                    Offset::new(0.0, -1.0),
1016                    Offset::new(1.0, 0.0),
1017                    offset + Offset::new(0.0, paint_extent),
1018                    true,
1019                ),
1020                AxisDirection::Right => {
1021                    (Offset::new(1.0, 0.0), Offset::new(0.0, 1.0), offset, false)
1022                }
1023                AxisDirection::Down => {
1024                    (Offset::new(0.0, 1.0), Offset::new(1.0, 0.0), offset, false)
1025                }
1026                AxisDirection::Left => (
1027                    Offset::new(-1.0, 0.0),
1028                    Offset::new(0.0, 1.0),
1029                    offset + Offset::new(paint_extent, 0.0),
1030                    true,
1031                ),
1032            };
1033        let mut child = self.first_child(app);
1034        while let Some(current) = child {
1035            let main_axis_delta = RenderSliverMultiBoxAdaptor::child_main_axis_position(
1036                self,
1037                app,
1038                current.as_object(),
1039            );
1040            let cross_axis_delta = self.child_cross_axis_position(app, current.as_object());
1041            let mut child_offset = Offset::new(
1042                origin_offset.dx()
1043                    + main_axis_unit.dx() * main_axis_delta
1044                    + cross_axis_unit.dx() * cross_axis_delta,
1045                origin_offset.dy()
1046                    + main_axis_unit.dy() * main_axis_delta
1047                    + cross_axis_unit.dy() * cross_axis_delta,
1048            );
1049            let child_paint_extent = self.paint_extent_of(app, current);
1050            if add_extent {
1051                child_offset = child_offset + main_axis_unit * child_paint_extent;
1052            }
1053
1054            // If the child's visible interval (main_axis_delta, main_axis_delta +
1055            // paint_extent_of(child)) does not intersect the paint extent interval
1056            // (0, constraints.remaining_paint_extent), it's hidden.
1057            if main_axis_delta < constraints.remaining_paint_extent
1058                && main_axis_delta + child_paint_extent > 0.0
1059            {
1060                context.paint_child(app, current.as_object(), child_offset);
1061            }
1062
1063            child = self.child_after(app, current);
1064        }
1065    }
1066
1067    /// Asserts that the reified child list is not empty and has a contiguous sequence of indices.
1068    ///
1069    /// Always returns true.
1070    fn debug_assert_child_list_is_non_empty_and_contiguous(
1071        self: RenderHandle<Self>,
1072        app: &App,
1073    ) -> bool {
1074        if cfg!(debug_assertions) {
1075            let first_child = self.first_child(app).expect("the child list is not empty");
1076            let mut index = self.index_of(app, first_child);
1077            let mut child = self.child_after(app, first_child);
1078            while let Some(current) = child {
1079                index += 1;
1080                debug_assert_eq!(self.index_of(app, current), index);
1081                child = self.child_after(app, current);
1082            }
1083        }
1084        true
1085    }
1086}
1087
1088#[cfg(test)]
1089pub(crate) mod test_support {
1090    use std::cell::{Cell, RefCell};
1091
1092    use inset_embedder::Size;
1093
1094    use super::*;
1095    use crate::box_::{RenderBox, RenderBoxData};
1096    use crate::object::{RenderObject, RenderObjectData};
1097
1098    /// A box that takes the largest size the constraints allow, and accepts hits.
1099    pub(crate) struct TestChild {
1100        render_object: RenderObjectData,
1101        render_box: RenderBoxData,
1102        preferred_extent: f64,
1103    }
1104
1105    impl RenderObject for TestChild {
1106        crate::render_object_accessors!();
1107
1108        fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
1109            let extent = self.get(app).preferred_extent;
1110            let size = self.constraints(app).constrain(Size::new(extent, extent));
1111            self.set_size(app, size);
1112        }
1113    }
1114
1115    impl RenderBox for TestChild {
1116        crate::render_box_accessors!();
1117
1118        fn hit_test_self(self: RenderHandle<Self>, _app: &App, _position: Offset) -> bool {
1119            let _ = self;
1120            true
1121        }
1122    }
1123
1124    /// A box that fills the constraints it is given, up to `extent` in each dimension, and
1125    /// accepts hits.
1126    pub(crate) fn hit_testable_box(app: &mut App, extent: f64) -> AnyRenderBox {
1127        RenderHandle::new_box(
1128            app,
1129            TestChild {
1130                render_object: RenderObjectData::new(),
1131                render_box: RenderBoxData::new(),
1132                preferred_extent: extent,
1133            },
1134        )
1135        .as_box()
1136    }
1137
1138    /// A [`RenderSliverBoxChildManager`] over a fixed list of child extents, as
1139    /// `SliverMultiBoxAdaptorElement` is over a delegate.
1140    pub(crate) struct TestChildManager<T: RenderSliverMultiBoxAdaptor> {
1141        render_object: Cell<Option<RenderHandle<T>>>,
1142        extents: Vec<f64>,
1143        currently_updating_child_index: Cell<Option<i32>>,
1144        /// The indices `create_child` was asked for, in order.
1145        pub(crate) created: RefCell<Vec<i32>>,
1146        /// The last value passed to `set_did_underflow`.
1147        pub(crate) did_underflow: Cell<bool>,
1148    }
1149
1150    impl<T: RenderSliverMultiBoxAdaptor> TestChildManager<T> {
1151        pub(crate) fn new(extents: Vec<f64>) -> Rc<TestChildManager<T>> {
1152            Rc::new(TestChildManager {
1153                render_object: Cell::new(None),
1154                extents,
1155                currently_updating_child_index: Cell::new(None),
1156                created: RefCell::new(Vec::new()),
1157                did_underflow: Cell::new(false),
1158            })
1159        }
1160
1161        pub(crate) fn attach(&self, render_object: RenderHandle<T>) {
1162            self.render_object.set(Some(render_object));
1163        }
1164
1165        fn adaptor(&self) -> RenderHandle<T> {
1166            self.render_object.get().expect("attached before layout")
1167        }
1168
1169        fn total_extent(&self) -> f64 {
1170            self.extents.iter().sum()
1171        }
1172    }
1173
1174    impl<T: RenderSliverMultiBoxAdaptor> RenderSliverBoxChildManager for TestChildManager<T> {
1175        fn create_child(&self, app: &mut App, index: i32, after: Option<AnyRenderBox>) {
1176            self.created.borrow_mut().push(index);
1177            let Ok(slot) = usize::try_from(index) else {
1178                return;
1179            };
1180            let Some(extent) = self.extents.get(slot).copied() else {
1181                return;
1182            };
1183            let child = RenderHandle::new_box(
1184                app,
1185                TestChild {
1186                    render_object: RenderObjectData::new(),
1187                    render_box: RenderBoxData::new(),
1188                    preferred_extent: extent,
1189                },
1190            )
1191            .as_box();
1192            self.currently_updating_child_index.set(Some(index));
1193            RenderSliverMultiBoxAdaptor::insert(self.adaptor(), app, child, after);
1194            self.currently_updating_child_index.set(None);
1195        }
1196
1197        fn remove_child(&self, app: &mut App, child: AnyRenderBox) {
1198            // Dart's element unmounts the child and disposes its render object later; the test
1199            // leaves the arena slot alone so that assertions can still name the child.
1200            RenderSliverMultiBoxAdaptor::remove(self.adaptor(), app, child);
1201        }
1202
1203        fn estimate_max_scroll_offset(
1204            &self,
1205            _app: &App,
1206            _constraints: SliverConstraints,
1207            _first_index: Option<i32>,
1208            _last_index: Option<i32>,
1209            _leading_scroll_offset: Option<f64>,
1210            _trailing_scroll_offset: Option<f64>,
1211        ) -> f64 {
1212            self.total_extent()
1213        }
1214
1215        fn child_count(&self, _app: &mut App) -> i32 {
1216            self.extents.len() as i32
1217        }
1218
1219        fn estimated_child_count(&self, _app: &App) -> Option<i32> {
1220            Some(self.extents.len() as i32)
1221        }
1222
1223        fn did_adopt_child(&self, app: &mut App, child: AnyRenderBox) {
1224            child
1225                .as_object()
1226                .parent_data_of_mut::<SliverMultiBoxAdaptorParentData>(app)
1227                .index = self.currently_updating_child_index.get();
1228        }
1229
1230        fn set_did_underflow(&self, _app: &mut App, value: bool) {
1231            self.did_underflow.set(value);
1232        }
1233    }
1234}
1235
1236#[cfg(test)]
1237pub(crate) mod viewport_test_support {
1238    use std::rc::Rc;
1239
1240    use inset_embedder::{Size, View, ViewId, ViewMetrics, ViewRef};
1241    use inset_foundation::Handle;
1242
1243    use super::*;
1244    use crate::box_::RenderBox;
1245    use crate::pipeline_owner::PipelineOwner;
1246    use crate::sliver::AnyRenderSliver;
1247    use crate::view::{RenderView, ViewConfiguration};
1248    use crate::viewport::RenderViewport;
1249    use crate::viewport_offset::{FixedViewportOffset, ViewportOffset};
1250    use inset_test::TestViewHost;
1251
1252    #[derive(Default)]
1253    struct TestView {
1254        host: TestViewHost,
1255    }
1256
1257    impl View for TestView {
1258        fn id(&self) -> ViewId {
1259            ViewId(0)
1260        }
1261
1262        fn metrics(&self) -> ViewMetrics {
1263            ViewMetrics::default()
1264        }
1265
1266        fn present(&self, _scene: std::sync::Arc<inset_embedder::Scene>) {}
1267
1268        fn host(&self) -> &dyn inset_embedder::ViewHost {
1269            &self.host
1270        }
1271    }
1272
1273    /// The scrolling machinery a lazy sliver needs: a viewport inside a [`RenderView`] rooted in
1274    /// a [`PipelineOwner`], laid out once.
1275    pub(crate) struct ScrollHarness {
1276        pub(crate) owner: Handle<PipelineOwner>,
1277        pub(crate) viewport: RenderHandle<RenderViewport>,
1278        pub(crate) offset: Handle<FixedViewportOffset>,
1279    }
1280
1281    impl ScrollHarness {
1282        /// Lays `sliver` out in a downward-scrolling viewport of `size`.
1283        pub(crate) fn new(app: &mut App, sliver: AnyRenderSliver, size: Size) -> ScrollHarness {
1284            let offset = FixedViewportOffset::zero(app);
1285            let viewport = RenderViewport::new(
1286                app,
1287                AxisDirection::Right,
1288                offset.as_viewport_offset(),
1289                Some(vec![sliver]),
1290                None,
1291            );
1292            let constraints = BoxConstraints::tight(size);
1293            let configuration = ViewConfiguration::new(constraints, constraints, 1.0);
1294            let view = RenderView::new(
1295                app,
1296                Some(viewport.as_box()),
1297                Some(configuration),
1298                Rc::new(TestView::default()) as ViewRef,
1299            );
1300            let owner = PipelineOwner::new(app, None, None);
1301            owner.set_root_node(app, Some(view.as_object()));
1302            view.prepare_initial_frame(app);
1303            owner.flush_layout(app);
1304            ScrollHarness {
1305                owner,
1306                viewport,
1307                offset,
1308            }
1309        }
1310
1311        /// Scrolls to `pixels` and lays out again.
1312        pub(crate) fn scroll_to(&self, app: &mut App, pixels: f64) {
1313            let current = self.offset.pixels(app);
1314            self.offset.correct_by(app, pixels - current);
1315            self.viewport.mark_needs_layout(app);
1316            self.owner.flush_layout(app);
1317        }
1318    }
1319}