Skip to main content

inset_rendering/
viewport.rs

1//! Flutter counterpart: `rendering/viewport.dart`.
2//!
3//! Semantics and debug paint wait; see `PORTING.md`.
4
5use std::any::{Any, TypeId};
6use std::fmt::{self, Debug};
7use std::rc::Rc;
8use std::time::Duration;
9
10use inset_animation::Curve;
11use inset_embedder::{Clip, Matrix4, Offset, Rect, Size, clamp_double};
12use inset_foundation::{App, Handle, HandleId, Listenable, Listener};
13use inset_painting::{Axis, AxisDirection, axis_direction_to_axis, transform_rect};
14
15use crate::box_::{BoxConstraints, BoxHitTestResult, RenderBox, RenderBoxData};
16use crate::debug::debug_check_has_bounded_axis;
17use crate::layer::{ClipRectLayer, LayerHandle};
18use crate::object::{
19    AnyRenderObject, ContainerRenderObjectData, ContainerRenderObjectMixin, RenderHandle,
20    RenderObject, RenderObjectData, debug_checking_intrinsics, resolve, translate,
21};
22use crate::painting_context::PaintingContext;
23use crate::pipeline_owner::PipelineOwner;
24use crate::sliver::{
25    AnyRenderSliver, GrowthDirection, SliverConstraints, SliverGeometry, SliverHitTestResult,
26    SliverLogicalContainerParentData, SliverPhysicalContainerParentData,
27    apply_growth_direction_to_axis_direction, apply_growth_direction_to_scroll_direction,
28};
29use crate::viewport_offset::AnyViewportOffset;
30
31/// The unit of measurement for a viewport's cache extent.
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
33pub enum CacheExtentStyle {
34    /// Treat the cache extent as logical pixels.
35    Pixel,
36
37    /// Treat the cache extent as a multiplier of the main axis extent.
38    Viewport,
39}
40
41/// The amount of additional content to display and lay out around the viewport.
42///
43/// The cache area is used to lay out slivers before they are visible on screen. Items that fall
44/// in this cache area are laid out even though they are not visible on screen. This allows the
45/// viewport to render them ahead of time, enabling a smoother scrolling experience.
46///
47/// This type encapsulates both the value and the style of the cache extent. It allows the cache
48/// extent to be defined either as a fixed number of logical pixels using
49/// [`ScrollCacheExtent::Pixels`] or as a multiplier of the viewport's main axis extent using
50/// [`ScrollCacheExtent::Viewport`].
51#[derive(Debug, Clone, Copy, PartialEq)]
52pub enum ScrollCacheExtent {
53    /// A cache extent in logical pixels.
54    Pixels(f64),
55
56    /// A cache extent as a multiplier of the viewport's main axis extent.
57    ///
58    /// The main axis extent is the size of the viewport in its main axis. For example, for a
59    /// vertically scrolling list, the main axis extent is the height of the viewport. If the
60    /// viewport is 600 logical pixels tall, then `ScrollCacheExtent::Viewport(2.0)` results in a
61    /// cache extent of 1200 logical pixels.
62    Viewport(f64),
63}
64
65impl ScrollCacheExtent {
66    /// Returns the cache extent in logical pixels for a given `main_axis_extent`.
67    fn calculate_cache_offset(self, main_axis_extent: f64) -> f64 {
68        match self {
69            ScrollCacheExtent::Pixels(pixels) => pixels,
70            ScrollCacheExtent::Viewport(value) => value * main_axis_extent,
71        }
72    }
73
74    /// Returns the style of the cache extent.
75    pub fn style(self) -> CacheExtentStyle {
76        match self {
77            ScrollCacheExtent::Pixels(_) => CacheExtentStyle::Pixel,
78            ScrollCacheExtent::Viewport(_) => CacheExtentStyle::Viewport,
79        }
80    }
81
82    /// Returns the raw value of the cache extent.
83    ///
84    /// If [`style`](Self::style) is [`CacheExtentStyle::Pixel`], this is the number of pixels to
85    /// cache. If it is [`CacheExtentStyle::Viewport`], this is the multiplier of the viewport's
86    /// main axis extent to cache.
87    pub fn value(self) -> f64 {
88        match self {
89            ScrollCacheExtent::Pixels(pixels) => pixels,
90            ScrollCacheExtent::Viewport(value) => value,
91        }
92    }
93}
94
95/// Specifies an order in which to paint the slivers of a viewport.
96///
97/// Whichever order the slivers are painted in, they will be hit-tested in the opposite order.
98///
99/// This can also be thought of as an ordering in the z-direction: whichever sliver is painted
100/// last (and hit-tested first) is on top, because it will paint over other slivers if there is
101/// overlap. Similarly, whichever sliver is painted first (and hit-tested last) is on the bottom.
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
103pub enum SliverPaintOrder {
104    /// The first sliver paints on top, and the last sliver on bottom.
105    ///
106    /// The slivers are painted in the reverse order of the viewport's children, and hit-tested
107    /// in the same order as the viewport's children.
108    ///
109    /// This is the default order.
110    #[default]
111    FirstIsTop,
112
113    /// The last sliver paints on top, and the first sliver on bottom.
114    ///
115    /// The slivers are painted in the same order as the viewport's children, and hit-tested in
116    /// the reverse order.
117    LastIsTop,
118}
119
120/// An interface for render objects that are bigger on the inside.
121///
122/// Some render objects, such as [`RenderViewport`], present a portion of their content, which
123/// can be controlled by a [`crate::ViewportOffset`]. This interface lets the framework recognize
124/// such render objects and interact with them without having specific knowledge of all the
125/// various types of viewports.
126///
127/// Answer [`RenderObject::interface`] for [`AnyRenderAbstractViewport`] with
128/// [`as_abstract_viewport`](Self::as_abstract_viewport) so that
129/// [`AnyRenderAbstractViewport::maybe_of`] can find this render object.
130pub trait RenderAbstractViewport: RenderObject {
131    /// Returns the offset that would be needed to reveal the `target` render object.
132    ///
133    /// This is used by [`show_in_viewport`], which is itself used by
134    /// [`RenderObject::show_on_screen`] for [`RenderViewportBase`].
135    ///
136    /// The optional `rect` parameter describes which area of that `target` object should be
137    /// revealed in the viewport. If `rect` is `None`, the entire `target` (as defined by its
138    /// paint bounds) will be revealed. If `rect` is provided it has to be given in the
139    /// coordinate system of the `target` object.
140    ///
141    /// The `alignment` argument describes where the target should be positioned after applying
142    /// the returned offset. If `alignment` is 0.0, the child must be positioned as close to the
143    /// leading edge of the viewport as possible. If `alignment` is 1.0, the child must be
144    /// positioned as close to the trailing edge of the viewport as possible. If `alignment` is
145    /// 0.5, the child must be positioned as close to the center of the viewport as possible.
146    ///
147    /// The `target` might not be a direct child of this viewport but it must be a descendant of
148    /// the viewport. Other viewports in between this viewport and the `target` will not be
149    /// adjusted.
150    ///
151    /// This method assumes that the content of the viewport moves linearly, i.e. when the offset
152    /// of the viewport is changed by x then `target` also moves by x within the viewport.
153    ///
154    /// The optional [`Axis`] determines which of two axes to compute an offset for.
155    /// One-dimensional viewports ignore it, since there is only one [`Axis`].
156    fn get_offset_to_reveal(
157        self: RenderHandle<Self>,
158        app: &App,
159        target: AnyRenderObject,
160        alignment: f64,
161        rect: Option<Rect>,
162        axis: Option<Axis>,
163    ) -> RevealedOffset;
164
165    /// The type-erased `RenderAbstractViewport` handle. Free: the vtable is a `const`, and the
166    /// id is copied.
167    fn as_abstract_viewport(self: RenderHandle<Self>) -> AnyRenderAbstractViewport {
168        AnyRenderAbstractViewport {
169            id: self.id(),
170            vtable: const { &RenderAbstractViewportVTable::of::<Self>() },
171        }
172    }
173}
174
175/// The vtable of an erased [`AnyRenderAbstractViewport`].
176struct RenderAbstractViewportVTable {
177    get_offset_to_reveal: GetOffsetToRevealFn,
178    as_object: fn(&App, HandleId) -> AnyRenderObject,
179}
180
181/// The dispatch signature of [`RenderAbstractViewport::get_offset_to_reveal`].
182type GetOffsetToRevealFn =
183    fn(&App, HandleId, AnyRenderObject, f64, Option<Rect>, Option<Axis>) -> RevealedOffset;
184
185impl RenderAbstractViewportVTable {
186    const fn of<T: RenderAbstractViewport>() -> RenderAbstractViewportVTable {
187        RenderAbstractViewportVTable {
188            get_offset_to_reveal: |app, id, target, alignment, rect, axis| {
189                T::get_offset_to_reveal(resolve(id), app, target, alignment, rect, axis)
190            },
191            as_object: |app, id| resolve::<T>(id).as_render_object(app),
192        }
193    }
194}
195
196/// Erased [`RenderAbstractViewport`]: what a field or parameter Dart types as
197/// `RenderAbstractViewport` becomes.
198#[derive(Clone, Copy)]
199pub struct AnyRenderAbstractViewport {
200    id: HandleId,
201    vtable: &'static RenderAbstractViewportVTable,
202}
203
204impl AnyRenderAbstractViewport {
205    /// The default value for the cache extent of a viewport.
206    ///
207    /// This default assumes [`CacheExtentStyle::Pixel`].
208    pub const DEFAULT_CACHE_EXTENT: f64 = 250.0;
209
210    /// Returns the viewport that most tightly encloses the given render object.
211    ///
212    /// If the object does not have a viewport as an ancestor, this returns `None`.
213    ///
214    /// Dart's `RenderAbstractViewport.maybeOf`.
215    pub fn maybe_of(
216        app: &App,
217        object: Option<AnyRenderObject>,
218    ) -> Option<AnyRenderAbstractViewport> {
219        let mut object = object;
220        while let Some(current) = object {
221            if let Some(viewport) = current.interface::<AnyRenderAbstractViewport>() {
222                return Some(viewport);
223            }
224            object = current.parent(app);
225        }
226        None
227    }
228
229    /// Returns the viewport that most tightly encloses the given render object.
230    ///
231    /// Dart's `RenderAbstractViewport.of`.
232    ///
233    /// # Panics
234    ///
235    /// If the object does not have a viewport as an ancestor.
236    pub fn of(app: &App, object: Option<AnyRenderObject>) -> AnyRenderAbstractViewport {
237        AnyRenderAbstractViewport::maybe_of(app, object).unwrap_or_else(|| {
238            panic!(
239                "RenderAbstractViewport::of was called with a render object that was not a \
240                 descendant of a RenderAbstractViewport: {object:?}"
241            )
242        })
243    }
244
245    /// The `RenderObject` view of this viewport.
246    pub fn as_object(self, app: &App) -> AnyRenderObject {
247        (self.vtable.as_object)(app, self.id)
248    }
249
250    /// See [`RenderAbstractViewport::get_offset_to_reveal`].
251    pub fn get_offset_to_reveal(
252        self,
253        app: &App,
254        target: AnyRenderObject,
255        alignment: f64,
256        rect: Option<Rect>,
257        axis: Option<Axis>,
258    ) -> RevealedOffset {
259        (self.vtable.get_offset_to_reveal)(app, self.id, target, alignment, rect, axis)
260    }
261}
262
263impl PartialEq for AnyRenderAbstractViewport {
264    fn eq(&self, other: &AnyRenderAbstractViewport) -> bool {
265        self.id == other.id
266    }
267}
268
269impl Eq for AnyRenderAbstractViewport {}
270
271impl Debug for AnyRenderAbstractViewport {
272    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273        write!(f, "AnyRenderAbstractViewport({:?})", self.id)
274    }
275}
276
277/// Return value for [`RenderAbstractViewport::get_offset_to_reveal`].
278///
279/// It indicates the [`offset`](Self::offset) required to reveal an element in a viewport and the
280/// [`rect`](Self::rect) position said element would have in the viewport at that offset.
281#[derive(Clone, Copy, Debug)]
282pub struct RevealedOffset {
283    /// Offset for the viewport to reveal a specific element in the viewport.
284    pub offset: f64,
285
286    /// The [`Rect`] in the outer coordinate system of the viewport at which the to-be-revealed
287    /// element would be located if the viewport's offset is set to [`offset`](Self::offset).
288    ///
289    /// A viewport usually has two coordinate systems and works as an adapter between the two:
290    ///
291    /// The inner coordinate system has its origin at the top left corner of the content that
292    /// moves inside the viewport. The origin of this coordinate system usually moves around
293    /// relative to the leading edge of the viewport when the viewport offset changes.
294    ///
295    /// The outer coordinate system has its origin at the top left corner of the visible part of
296    /// the viewport. This origin stays at the same position regardless of the current viewport
297    /// offset.
298    pub rect: Rect,
299}
300
301impl RevealedOffset {
302    /// Instantiates a return value for [`RenderAbstractViewport::get_offset_to_reveal`].
303    pub const fn new(offset: f64, rect: Rect) -> RevealedOffset {
304        RevealedOffset { offset, rect }
305    }
306
307    /// Determines which provided leading or trailing edge of the viewport will be used for
308    /// [`show_in_viewport`], accounting for the size and already visible portion of the render
309    /// object that is being revealed.
310    ///
311    /// If the target render object is already fully visible, this returns `None`.
312    pub fn clamp_offset(
313        leading_edge_offset: RevealedOffset,
314        trailing_edge_offset: RevealedOffset,
315        current_offset: f64,
316    ) -> Option<RevealedOffset> {
317        //           scrollOffset
318        //                       0 +---------+
319        //                         |         |
320        //                       _ |         |
321        //    viewport position |  |         |
322        // with `descendant` at |  |         | _
323        //        trailing edge |_ | xxxxxxx |  | viewport position
324        //                         |         |  | with `descendant` at
325        //                         |         | _| leading edge
326        //                         |         |
327        //                     800 +---------+
328        //
329        // `trailing_edge_offset`: Distance from scrollOffset 0 to the start of the viewport on
330        //                         the left in the image above.
331        // `leading_edge_offset`: Distance from scrollOffset 0 to the start of the viewport on
332        //                        the right in the image above.
333        //
334        // The viewport position on the left is achieved by setting `offset.pixels` to
335        // `trailing_edge_offset`, the one on the right by setting it to `leading_edge_offset`.
336        let inverted = leading_edge_offset.offset < trailing_edge_offset.offset;
337        let (smaller, larger) = if inverted {
338            (leading_edge_offset, trailing_edge_offset)
339        } else {
340            (trailing_edge_offset, leading_edge_offset)
341        };
342        if current_offset > larger.offset {
343            Some(larger)
344        } else if current_offset < smaller.offset {
345            Some(smaller)
346        } else {
347            None
348        }
349    }
350}
351
352/// Flutter's `layoutChildSequence`'s `advance` callback: `childBefore` or `childAfter`.
353pub type SliverAdvance<T> =
354    dyn Fn(RenderHandle<T>, &App, AnyRenderSliver) -> Option<AnyRenderSliver>;
355
356/// Flutter's `RenderViewportBase` fields.
357pub struct RenderViewportBaseData {
358    axis_direction: AxisDirection,
359    cross_axis_direction: AxisDirection,
360    offset: AnyViewportOffset,
361    scroll_cache_extent: ScrollCacheExtent,
362    /// This value is set during layout based on the
363    /// [`scroll_cache_extent`](RenderViewportBase::scroll_cache_extent).
364    ///
365    /// When the style is [`CacheExtentStyle::Viewport`], it is the main axis extent of the
366    /// viewport multiplied by the requested cache extent, which is still expressed in pixels.
367    calculated_cache_extent: Option<f64>,
368    paint_order: SliverPaintOrder,
369    clip_behavior: Clip,
370    clip_rect_layer: LayerHandle<Handle<ClipRectLayer>>,
371}
372
373impl RenderViewportBaseData {
374    /// Initializes fields with Flutter's constructor defaults: an [`AxisDirection::Down`] axis,
375    /// a [`AnyRenderAbstractViewport::DEFAULT_CACHE_EXTENT`] pixel cache extent,
376    /// [`SliverPaintOrder::FirstIsTop`], and [`Clip::HardEdge`].
377    pub fn new(
378        cross_axis_direction: AxisDirection,
379        offset: AnyViewportOffset,
380    ) -> RenderViewportBaseData {
381        RenderViewportBaseData {
382            axis_direction: AxisDirection::Down,
383            cross_axis_direction,
384            offset,
385            scroll_cache_extent: ScrollCacheExtent::Pixels(
386                AnyRenderAbstractViewport::DEFAULT_CACHE_EXTENT,
387            ),
388            calculated_cache_extent: None,
389            paint_order: SliverPaintOrder::FirstIsTop,
390            clip_behavior: Clip::HardEdge,
391            clip_rect_layer: LayerHandle::new(),
392        }
393    }
394}
395
396/// Implements [`RenderViewportBase`] field accessors for a `viewport` field.
397#[macro_export]
398macro_rules! render_viewport_base_accessors {
399    () => {
400        fn viewport_data(
401            self: $crate::RenderHandle<Self>,
402            app: &::inset_foundation::App,
403        ) -> &$crate::RenderViewportBaseData {
404            &self.get(app).viewport
405        }
406        fn viewport_data_mut(
407            self: $crate::RenderHandle<Self>,
408            app: &mut ::inset_foundation::App,
409        ) -> &mut $crate::RenderViewportBaseData {
410            &mut self.get_mut(app).viewport
411        }
412    };
413}
414
415/// A base class for render objects that are bigger on the inside.
416///
417/// This render object provides the shared code for render objects that host [`AnyRenderSliver`]
418/// render objects inside a [`RenderBox`]. The viewport establishes an
419/// [`axis_direction`](Self::axis_direction), which orients the sliver's coordinate system, which
420/// is based on scroll offsets rather than Cartesian coordinates.
421///
422/// The viewport also listens to an [`offset`](Self::offset), which determines the
423/// [`SliverConstraints::scroll_offset`] input to the sliver layout protocol.
424///
425/// Implementors typically override [`RenderObject::perform_layout`] and call
426/// [`layout_child_sequence`](Self::layout_child_sequence), perhaps multiple times.
427///
428/// Flutter's `RenderViewportBase`; the state lives in a [`RenderViewportBaseData`] field.
429pub trait RenderViewportBase:
430    RenderBox + RenderAbstractViewport + ContainerRenderObjectMixin<ChildType = AnyRenderSliver>
431{
432    /// Mixin field access.
433    fn viewport_data(self: RenderHandle<Self>, app: &App) -> &RenderViewportBaseData;
434
435    /// See [`viewport_data`](Self::viewport_data).
436    fn viewport_data_mut(self: RenderHandle<Self>, app: &mut App) -> &mut RenderViewportBaseData;
437
438    /// The direction in which the [`SliverConstraints::scroll_offset`] increases.
439    ///
440    /// For example, if the axis direction is [`AxisDirection::Down`], a scroll offset of zero is
441    /// at the top of the viewport and increases towards the bottom of the viewport.
442    fn axis_direction(self: RenderHandle<Self>, app: &App) -> AxisDirection {
443        self.viewport_data(app).axis_direction
444    }
445
446    /// Sets [`axis_direction`](Self::axis_direction).
447    fn set_axis_direction(self: RenderHandle<Self>, app: &mut App, value: AxisDirection) {
448        if value == self.axis_direction(app) {
449            return;
450        }
451        self.viewport_data_mut(app).axis_direction = value;
452        self.mark_needs_layout(app);
453    }
454
455    /// The direction in which child should be laid out in the cross axis.
456    ///
457    /// For example, if the [`axis_direction`](Self::axis_direction) is [`AxisDirection::Down`],
458    /// this property is typically [`AxisDirection::Left`] if the ambient text direction is
459    /// right-to-left and [`AxisDirection::Right`] if it is left-to-right.
460    fn cross_axis_direction(self: RenderHandle<Self>, app: &App) -> AxisDirection {
461        self.viewport_data(app).cross_axis_direction
462    }
463
464    /// Sets [`cross_axis_direction`](Self::cross_axis_direction).
465    fn set_cross_axis_direction(self: RenderHandle<Self>, app: &mut App, value: AxisDirection) {
466        if value == self.cross_axis_direction(app) {
467            return;
468        }
469        self.viewport_data_mut(app).cross_axis_direction = value;
470        self.mark_needs_layout(app);
471    }
472
473    /// The axis along which the viewport scrolls.
474    ///
475    /// For example, if the [`axis_direction`](Self::axis_direction) is [`AxisDirection::Down`],
476    /// then the axis is [`Axis::Vertical`] and the viewport scrolls vertically.
477    fn axis(self: RenderHandle<Self>, app: &App) -> Axis {
478        axis_direction_to_axis(self.axis_direction(app))
479    }
480
481    /// Which part of the content inside the viewport should be visible.
482    ///
483    /// The [`crate::ViewportOffset::pixels`] value determines the scroll offset that the
484    /// viewport uses to select which part of its content to display. As the user scrolls the
485    /// viewport, this value changes, which changes the content that is displayed.
486    fn offset(self: RenderHandle<Self>, app: &App) -> AnyViewportOffset {
487        self.viewport_data(app).offset
488    }
489
490    /// Sets [`offset`](Self::offset).
491    fn set_offset(self: RenderHandle<Self>, app: &mut App, value: AnyViewportOffset) {
492        let old = self.offset(app);
493        if value == old {
494            return;
495        }
496        if self.attached(app) {
497            old.remove_listener(app, &self.mark_needs_layout_listener());
498        }
499        self.viewport_data_mut(app).offset = value;
500        if self.attached(app) {
501            value.add_listener(app, self.mark_needs_layout_listener());
502        }
503        // We need to go through layout even if the new offset has the same pixels value as the
504        // old offset so that we will apply our viewport and content dimensions.
505        self.mark_needs_layout(app);
506    }
507
508    /// Dart's `_offset.addListener(markNeedsLayout)` tear-off.
509    fn mark_needs_layout_listener(self: RenderHandle<Self>) -> Listener {
510        Listener::handle_method(self.handle(), viewport_offset_changed::<Self>)
511    }
512
513    /// The viewport has an area before and after the visible area to cache items that are about
514    /// to become visible when the user scrolls.
515    ///
516    /// Items that fall in this cache area are laid out even though they are not (yet) visible on
517    /// screen. The scroll cache extent describes how much the cache area extends before the
518    /// leading edge and after the trailing edge of the viewport.
519    ///
520    /// The total extent, which the viewport will try to cover with children, is the scroll cache
521    /// extent before the leading edge + extent of the main axis + the scroll cache extent after
522    /// the trailing edge.
523    fn scroll_cache_extent(self: RenderHandle<Self>, app: &App) -> ScrollCacheExtent {
524        self.viewport_data(app).scroll_cache_extent
525    }
526
527    /// Sets [`scroll_cache_extent`](Self::scroll_cache_extent).
528    ///
529    /// `None` resets the value to [`AnyRenderAbstractViewport::DEFAULT_CACHE_EXTENT`] pixels.
530    fn set_scroll_cache_extent(
531        self: RenderHandle<Self>,
532        app: &mut App,
533        value: Option<ScrollCacheExtent>,
534    ) {
535        let effective_value = value.unwrap_or(ScrollCacheExtent::Pixels(
536            AnyRenderAbstractViewport::DEFAULT_CACHE_EXTENT,
537        ));
538        if effective_value == self.scroll_cache_extent(app) {
539            return;
540        }
541        self.viewport_data_mut(app).scroll_cache_extent = effective_value;
542        self.mark_needs_layout(app);
543    }
544
545    /// Deprecated. Use [`scroll_cache_extent`](Self::scroll_cache_extent) instead.
546    #[deprecated(note = "Use scroll_cache_extent instead.")]
547    fn cache_extent(self: RenderHandle<Self>, app: &App) -> f64 {
548        self.scroll_cache_extent(app).value()
549    }
550
551    /// Deprecated. Use [`set_scroll_cache_extent`](Self::set_scroll_cache_extent) instead.
552    ///
553    /// `None` resets the value to [`AnyRenderAbstractViewport::DEFAULT_CACHE_EXTENT`] pixels, in
554    /// which case the style becomes [`CacheExtentStyle::Pixel`].
555    #[deprecated(note = "Use set_scroll_cache_extent instead.")]
556    fn set_cache_extent(self: RenderHandle<Self>, app: &mut App, value: Option<f64>) {
557        let current = self.scroll_cache_extent(app);
558        if value == Some(current.value()) {
559            return;
560        }
561        let replacement = match value {
562            None => ScrollCacheExtent::Pixels(AnyRenderAbstractViewport::DEFAULT_CACHE_EXTENT),
563            Some(value) => match current {
564                ScrollCacheExtent::Pixels(_) => ScrollCacheExtent::Pixels(value),
565                ScrollCacheExtent::Viewport(_) => ScrollCacheExtent::Viewport(value),
566            },
567        };
568        self.viewport_data_mut(app).scroll_cache_extent = replacement;
569        self.mark_needs_layout(app);
570    }
571
572    /// Deprecated. Use [`scroll_cache_extent`](Self::scroll_cache_extent) instead.
573    #[deprecated(note = "Use scroll_cache_extent instead.")]
574    fn cache_extent_style(self: RenderHandle<Self>, app: &App) -> CacheExtentStyle {
575        self.scroll_cache_extent(app).style()
576    }
577
578    /// Deprecated. Use [`set_scroll_cache_extent`](Self::set_scroll_cache_extent) instead.
579    #[deprecated(note = "Use set_scroll_cache_extent instead.")]
580    fn set_cache_extent_style(self: RenderHandle<Self>, app: &mut App, value: CacheExtentStyle) {
581        let current = self.scroll_cache_extent(app);
582        if value == current.style() {
583            return;
584        }
585        self.viewport_data_mut(app).scroll_cache_extent = match value {
586            CacheExtentStyle::Pixel => ScrollCacheExtent::Pixels(current.value()),
587            CacheExtentStyle::Viewport => ScrollCacheExtent::Viewport(current.value()),
588        };
589        self.mark_needs_layout(app);
590    }
591
592    /// The order in which to paint the slivers; equivalently, the order in which to arrange them
593    /// in the z-direction.
594    ///
595    /// Whichever order the slivers are painted in, they will be hit-tested in the opposite
596    /// order.
597    ///
598    /// The default is [`SliverPaintOrder::FirstIsTop`].
599    fn paint_order(self: RenderHandle<Self>, app: &App) -> SliverPaintOrder {
600        self.viewport_data(app).paint_order
601    }
602
603    /// Sets [`paint_order`](Self::paint_order).
604    fn set_paint_order(self: RenderHandle<Self>, app: &mut App, value: SliverPaintOrder) {
605        if value == self.paint_order(app) {
606            return;
607        }
608        self.viewport_data_mut(app).paint_order = value;
609        self.mark_needs_paint(app);
610    }
611
612    /// The content will be clipped (or not) according to this option.
613    ///
614    /// Defaults to [`Clip::HardEdge`].
615    fn clip_behavior(self: RenderHandle<Self>, app: &App) -> Clip {
616        self.viewport_data(app).clip_behavior
617    }
618
619    /// Sets [`clip_behavior`](Self::clip_behavior).
620    fn set_clip_behavior(self: RenderHandle<Self>, app: &mut App, value: Clip) {
621        if value == self.clip_behavior(app) {
622            return;
623        }
624        self.viewport_data_mut(app).clip_behavior = value;
625        self.mark_needs_paint(app);
626    }
627
628    /// The body of Flutter's `attach` override: listens to the offset.
629    fn did_attach(
630        self: RenderHandle<Self>,
631        app: &mut App,
632        owner: inset_foundation::Handle<PipelineOwner>,
633    ) {
634        ContainerRenderObjectMixin::did_attach(self, app, owner);
635        self.offset(app)
636            .add_listener(app, self.mark_needs_layout_listener());
637    }
638
639    /// The body of Flutter's `detach` override: stops listening to the offset.
640    fn did_detach(self: RenderHandle<Self>, app: &mut App) {
641        self.offset(app)
642            .remove_listener(app, &self.mark_needs_layout_listener());
643        ContainerRenderObjectMixin::did_detach(self, app);
644    }
645
646    /// Panics saying that the object does not support returning intrinsic dimensions if, in
647    /// debug mode, we are not in the [`debug_checking_intrinsics`] mode.
648    ///
649    /// This is used by [`compute_min_intrinsic_width`](Self::compute_min_intrinsic_width) et al
650    /// because viewports do not generally support returning intrinsic dimensions. See the
651    /// discussion at [`RenderBox::compute_min_intrinsic_width`].
652    fn debug_throw_if_not_checking_intrinsics(self: RenderHandle<Self>) -> bool {
653        let _ = self;
654        debug_assert!(
655            debug_checking_intrinsics(),
656            "{} does not support returning intrinsic dimensions.\n\
657             Calculating the intrinsic dimensions would require instantiating every child of \
658             the viewport, which defeats the point of viewports being lazy.\n\
659             If you are merely trying to shrink-wrap the viewport in the main axis direction, \
660             consider a RenderShrinkWrappingViewport render object (ShrinkWrappingViewport \
661             widget), which achieves that effect without implementing the intrinsic dimension \
662             API.",
663            std::any::type_name::<Self>()
664        );
665        true
666    }
667
668    /// Zero; a viewport has no intrinsic dimensions.
669    fn compute_min_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
670        let _ = (app, height);
671        debug_assert!(self.debug_throw_if_not_checking_intrinsics());
672        0.0
673    }
674
675    /// Zero; a viewport has no intrinsic dimensions.
676    fn compute_max_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
677        let _ = (app, height);
678        debug_assert!(self.debug_throw_if_not_checking_intrinsics());
679        0.0
680    }
681
682    /// Zero; a viewport has no intrinsic dimensions.
683    fn compute_min_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
684        let _ = (app, width);
685        debug_assert!(self.debug_throw_if_not_checking_intrinsics());
686        0.0
687    }
688
689    /// Zero; a viewport has no intrinsic dimensions.
690    fn compute_max_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
691        let _ = (app, width);
692        debug_assert!(self.debug_throw_if_not_checking_intrinsics());
693        0.0
694    }
695
696    /// Determines the size and position of some of the children of the viewport.
697    ///
698    /// This function is the workhorse of `perform_layout` implementations in implementors.
699    ///
700    /// Layout starts with `child`, proceeds according to the `advance` callback, and stops once
701    /// `advance` returns `None`.
702    ///
703    ///  * `scroll_offset` is the [`SliverConstraints::scroll_offset`] to pass the first child.
704    ///    The scroll offset is adjusted by [`SliverGeometry::scroll_extent`] for subsequent
705    ///    children.
706    ///  * `overlap` is the [`SliverConstraints::overlap`] to pass the first child. The overlap
707    ///    is adjusted by the [`SliverGeometry::paint_origin`] and
708    ///    [`SliverGeometry::paint_extent`] for subsequent children.
709    ///  * `layout_offset` is the layout offset at which to place the first child. The layout
710    ///    offset is updated by the [`SliverGeometry::layout_extent`] for subsequent children.
711    ///  * `remaining_paint_extent` is [`SliverConstraints::remaining_paint_extent`] to pass the
712    ///    first child. The remaining paint extent is updated by the
713    ///    [`SliverGeometry::layout_extent`] for subsequent children.
714    ///  * `main_axis_extent` is the [`SliverConstraints::viewport_main_axis_extent`] to pass to
715    ///    each child.
716    ///  * `cross_axis_extent` is the [`SliverConstraints::cross_axis_extent`] to pass to each
717    ///    child.
718    ///  * `growth_direction` is the [`SliverConstraints::growth_direction`] to pass to each
719    ///    child.
720    ///
721    /// Returns the first non-zero [`SliverGeometry::scroll_offset_correction`] encountered, if
722    /// any. Otherwise returns 0.0. Typical callers will call this function repeatedly until it
723    /// returns 0.0.
724    #[allow(clippy::too_many_arguments)]
725    fn layout_child_sequence(
726        self: RenderHandle<Self>,
727        app: &mut App,
728        child: Option<AnyRenderSliver>,
729        scroll_offset: f64,
730        overlap: f64,
731        layout_offset: f64,
732        remaining_paint_extent: f64,
733        main_axis_extent: f64,
734        cross_axis_extent: f64,
735        growth_direction: GrowthDirection,
736        advance: &SliverAdvance<Self>,
737        remaining_cache_extent: f64,
738        cache_origin: f64,
739    ) -> f64 {
740        debug_assert!(scroll_offset.is_finite());
741        debug_assert!(scroll_offset >= 0.0);
742        let initial_layout_offset = layout_offset;
743        let adjusted_user_scroll_direction = apply_growth_direction_to_scroll_direction(
744            self.offset(app).user_scroll_direction(app),
745            growth_direction,
746        );
747        let axis_direction = self.axis_direction(app);
748        let cross_axis_direction = self.cross_axis_direction(app);
749        let mut max_paint_offset = layout_offset + overlap;
750        let mut preceding_scroll_extent = 0.0;
751        let mut scroll_offset = scroll_offset;
752        let mut layout_offset = layout_offset;
753        let mut remaining_cache_extent = remaining_cache_extent;
754        let mut cache_origin = cache_origin;
755        let mut child = child;
756
757        while let Some(current) = child {
758            let sliver_scroll_offset = scroll_offset.max(0.0);
759            // If the scrollOffset is too small we adjust the paddedOrigin because it doesn't
760            // make sense to ask a sliver for content before its scroll offset.
761            let corrected_cache_origin = cache_origin.max(-sliver_scroll_offset);
762            let cache_extent_correction = cache_origin - corrected_cache_origin;
763
764            debug_assert!(sliver_scroll_offset >= corrected_cache_origin.abs());
765            debug_assert!(corrected_cache_origin <= 0.0);
766            debug_assert!(sliver_scroll_offset >= 0.0);
767            debug_assert!(cache_extent_correction <= 0.0);
768
769            current.layout(
770                app,
771                SliverConstraints::new(
772                    axis_direction,
773                    growth_direction,
774                    adjusted_user_scroll_direction,
775                    sliver_scroll_offset,
776                    preceding_scroll_extent,
777                    max_paint_offset - layout_offset,
778                    (remaining_paint_extent - layout_offset + initial_layout_offset).max(0.0),
779                    cross_axis_extent,
780                    cross_axis_direction,
781                    main_axis_extent,
782                    (remaining_cache_extent + cache_extent_correction).max(0.0),
783                    corrected_cache_origin,
784                ),
785                true,
786            );
787
788            let child_layout_geometry = current.geometry(app);
789            debug_assert!(child_layout_geometry.debug_assert_is_valid());
790
791            // If there is a correction to apply, we'll have to start over.
792            if let Some(correction) = child_layout_geometry.scroll_offset_correction {
793                return correction;
794            }
795
796            // We use the child's paint origin in our coordinate system as the layoutOffset we
797            // store in the child's parent data.
798            let effective_layout_offset = layout_offset + child_layout_geometry.paint_origin;
799
800            // `effective_layout_offset` becomes meaningless once we moved past the trailing edge
801            // because `child_layout_geometry.layout_extent` is zero. Using the still increasing
802            // `scroll_offset` to roughly position these invisible slivers in the right order.
803            if child_layout_geometry.visible || scroll_offset > 0.0 {
804                self.update_child_layout_offset(
805                    app,
806                    current,
807                    effective_layout_offset,
808                    growth_direction,
809                );
810            } else {
811                self.update_child_layout_offset(
812                    app,
813                    current,
814                    -scroll_offset + initial_layout_offset,
815                    growth_direction,
816                );
817            }
818
819            max_paint_offset = (effective_layout_offset + child_layout_geometry.paint_extent)
820                .max(max_paint_offset);
821            scroll_offset -= child_layout_geometry.scroll_extent;
822            preceding_scroll_extent += child_layout_geometry.scroll_extent;
823            layout_offset += child_layout_geometry.layout_extent;
824            if child_layout_geometry.cache_extent != 0.0 {
825                remaining_cache_extent -=
826                    child_layout_geometry.cache_extent - cache_extent_correction;
827                cache_origin =
828                    (corrected_cache_origin + child_layout_geometry.cache_extent).min(0.0);
829            }
830
831            self.update_out_of_band_data(app, growth_direction, child_layout_geometry);
832
833            // move on to the next child
834            child = advance(self, app, current);
835        }
836
837        // we made it without a correction, whee!
838        0.0
839    }
840
841    /// The body of Flutter's `paint` override.
842    fn paint(
843        self: RenderHandle<Self>,
844        app: &mut App,
845        context: &mut PaintingContext,
846        offset: Offset,
847    ) {
848        if self.first_child(app).is_none() {
849            return;
850        }
851        let clip_behavior = self.clip_behavior(app);
852        if self.has_visual_overflow(app) && clip_behavior != Clip::None {
853            let bounds = Offset::ZERO & self.size(app);
854            let old = self.viewport_data(app).clip_rect_layer.layer();
855            let layer = context.push_clip_rect(
856                app,
857                self.as_object().needs_compositing(app),
858                offset,
859                bounds,
860                |app, context, offset| self.paint_contents(app, context, offset),
861                clip_behavior,
862                old,
863            );
864            LayerHandle::set_layer(
865                app,
866                |app| &mut self.viewport_data_mut(app).clip_rect_layer,
867                layer,
868            );
869        } else {
870            LayerHandle::set_layer(
871                app,
872                |app| &mut self.viewport_data_mut(app).clip_rect_layer,
873                None,
874            );
875            self.paint_contents(app, context, offset);
876        }
877    }
878
879    /// Flutter's `dispose`: drop the clip layer handle, then `super.dispose()`.
880    fn dispose(self: RenderHandle<Self>, app: &mut App) {
881        LayerHandle::set_layer(
882            app,
883            |app| &mut self.viewport_data_mut(app).clip_rect_layer,
884            None,
885        );
886        crate::object::RenderObjectBase::dispose(self, app);
887    }
888
889    /// Flutter's `_paintContents`.
890    fn paint_contents(
891        self: RenderHandle<Self>,
892        app: &mut App,
893        context: &mut PaintingContext,
894        offset: Offset,
895    ) {
896        for child in self.children_in_paint_order(app) {
897            if child.geometry(app).visible {
898                let child_offset = offset + self.paint_offset_of(app, child);
899                context.paint_child(app, child.as_object(), child_offset);
900            }
901        }
902    }
903
904    /// The body of Flutter's `hitTestChildren` override.
905    fn hit_test_children(
906        self: RenderHandle<Self>,
907        app: &mut App,
908        result: &mut BoxHitTestResult<'_>,
909        position: Offset,
910    ) -> bool {
911        let (main_axis_position, cross_axis_position) = match self.axis(app) {
912            Axis::Vertical => (position.dy(), position.dx()),
913            Axis::Horizontal => (position.dx(), position.dy()),
914        };
915        for child in self.children_in_hit_test_order(app) {
916            if !child.geometry(app).visible {
917                continue;
918            }
919            let mut transform = Matrix4::IDENTITY;
920            // must be invertible
921            self.as_object()
922                .apply_paint_transform(app, child.as_object(), &mut transform);
923            let child_main_axis_position =
924                self.compute_child_main_axis_position(app, child, main_axis_position);
925            let is_hit =
926                result.add_with_out_of_band_position(None, Some(transform), None, |result| {
927                    child.hit_test(
928                        app,
929                        &mut SliverHitTestResult::wrap(result),
930                        child_main_axis_position,
931                        cross_axis_position,
932                    )
933                });
934            if is_hit {
935                return true;
936            }
937        }
938        false
939    }
940
941    /// The body of Flutter's `getOffsetToReveal` override.
942    fn get_offset_to_reveal(
943        self: RenderHandle<Self>,
944        app: &App,
945        target: AnyRenderObject,
946        alignment: f64,
947        rect: Option<Rect>,
948        _axis: Option<Axis>,
949    ) -> RevealedOffset {
950        // One dimensional viewport has only one axis, override if it was provided/may be
951        // mismatched.
952        let axis = self.axis(app);
953        let this = self.as_object();
954
955        // Steps to convert `rect` (from a RenderBox coordinate system) to its scroll offset
956        // within this viewport (not in the exact order):
957        //
958        // 1. Pick the outermost RenderBox (between which, and the viewport, there is nothing but
959        // RenderSlivers) as an intermediate reference frame (the `pivot`), convert `rect` to
960        // that coordinate space.
961        //
962        // 2. Convert `rect` from the `pivot` coordinate space to its sliver parent's sliver
963        // coordinate system (i.e., to a scroll offset), based on the axis direction and growth
964        // direction of the parent.
965        //
966        // 3. Convert the scroll offset to its sliver parent's coordinate space using
967        // `child_scroll_offset`, until we reach the viewport.
968        //
969        // 4. Make the final conversion from the outmost sliver to the viewport using
970        // `scroll_offset_of`.
971
972        let mut leading_scroll_offset = 0.0;
973        // Starting at `target` and walking towards the root:
974        //  - `child` will be the last object before we reach this viewport, and
975        //  - `pivot` will be the last RenderBox before we reach this viewport.
976        let mut child = target;
977        let mut pivot = None;
978        // ... between viewport and `target` (`target` included).
979        let mut only_slivers = target.as_sliver().is_some();
980        let mut rect = rect;
981        while child.parent(app) != Some(this) {
982            let parent = child
983                .parent(app)
984                .expect("target is a descendant of this viewport");
985            if let Some(box_child) = child.as_box() {
986                pivot = Some(box_child);
987            }
988            match parent.as_sliver() {
989                Some(sliver) => {
990                    leading_scroll_offset += sliver
991                        .child_scroll_offset(app, child)
992                        .expect("a sliver parent reports its child's scroll offset");
993                }
994                None => {
995                    only_slivers = false;
996                    leading_scroll_offset = 0.0;
997                }
998            }
999            child = parent;
1000        }
1001
1002        // `rect` in the new intermediate coordinate system.
1003        let rect_local;
1004        // Our new reference frame render object's main axis extent.
1005        let pivot_extent;
1006        let growth_direction;
1007
1008        // `leading_scroll_offset` is currently the scrollOffset of our new reference frame
1009        // (`pivot` or `target`), within `child`.
1010        if let Some(pivot) = pivot {
1011            debug_assert!(pivot.as_object().parent(app).is_some());
1012            debug_assert_ne!(pivot.as_object().parent(app), Some(this));
1013            debug_assert_ne!(pivot.as_object(), this);
1014            let pivot_parent = pivot
1015                .as_object()
1016                .parent(app)
1017                .and_then(AnyRenderObject::as_sliver)
1018                .expect("only sliver parents are supported between a pivot and the viewport");
1019            growth_direction = pivot_parent.constraints(app).growth_direction;
1020            pivot_extent = match axis {
1021                Axis::Horizontal => pivot.size(app).width(),
1022                Axis::Vertical => pivot.size(app).height(),
1023            };
1024            let rect = *rect.get_or_insert_with(|| target.paint_bounds(app));
1025            rect_local =
1026                transform_rect(&target.get_transform_to(app, Some(pivot.as_object())), rect);
1027        } else if only_slivers {
1028            // `pivot` does not exist. We'll have to make up one from `target`, the innermost
1029            // sliver.
1030            let target_sliver = target.as_sliver().expect("only_slivers");
1031            growth_direction = target_sliver.constraints(app).growth_direction;
1032            pivot_extent = target_sliver.geometry(app).scroll_extent;
1033            rect_local = *rect.get_or_insert_with(|| {
1034                let constraints = target_sliver.constraints(app);
1035                let geometry = target_sliver.geometry(app);
1036                match axis {
1037                    Axis::Horizontal => Rect::from_ltwh(
1038                        0.0,
1039                        0.0,
1040                        geometry.scroll_extent,
1041                        constraints.cross_axis_extent,
1042                    ),
1043                    Axis::Vertical => Rect::from_ltwh(
1044                        0.0,
1045                        0.0,
1046                        constraints.cross_axis_extent,
1047                        geometry.scroll_extent,
1048                    ),
1049                }
1050            });
1051        } else {
1052            let rect = rect.expect("a non-sliver target must be given a rect");
1053            return RevealedOffset::new(self.offset(app).pixels(app), rect);
1054        }
1055
1056        debug_assert_eq!(child.parent(app), Some(this));
1057        let sliver = child
1058            .as_sliver()
1059            .expect("a viewport's children are slivers");
1060
1061        // The scroll offset of `rect` within `child`.
1062        leading_scroll_offset += match apply_growth_direction_to_axis_direction(
1063            self.axis_direction(app),
1064            growth_direction,
1065        ) {
1066            AxisDirection::Up => pivot_extent - rect_local.bottom,
1067            AxisDirection::Left => pivot_extent - rect_local.right,
1068            AxisDirection::Right => rect_local.left,
1069            AxisDirection::Down => rect_local.top,
1070        };
1071
1072        // So far leading_scroll_offset is the scroll offset of `rect` in the `child` sliver's
1073        // sliver coordinate system. The sign of this value indicates whether the `rect`
1074        // protrudes the leading edge of the `child` sliver. When this value is non-negative and
1075        // `child`'s `max_scroll_obstruction_extent` is greater than 0, we assume `rect` can't be
1076        // obstructed by the leading edge of the viewport (i.e. it is pinned to the leading edge).
1077        let is_pinned = sliver.geometry(app).max_scroll_obstruction_extent > 0.0
1078            && leading_scroll_offset >= 0.0;
1079
1080        // The scroll offset in the viewport to `rect`.
1081        leading_scroll_offset = self.scroll_offset_of(app, sliver, leading_scroll_offset);
1082
1083        // This step assumes the viewport's layout is up-to-date, i.e., if offset.pixels is
1084        // changed after the last perform_layout, the new scroll position will not be accounted
1085        // for.
1086        let transform = target.get_transform_to(app, Some(this));
1087        let mut target_rect = transform_rect(&transform, rect.expect("set above"));
1088        let extent_of_pinned_slivers = self.max_scroll_obstruction_extent_before(app, sliver);
1089
1090        match sliver.constraints(app).growth_direction {
1091            GrowthDirection::Forward => {
1092                if is_pinned && alignment <= 0.0 {
1093                    return RevealedOffset::new(f64::INFINITY, target_rect);
1094                }
1095                leading_scroll_offset -= extent_of_pinned_slivers;
1096            }
1097            GrowthDirection::Reverse => {
1098                if is_pinned && alignment >= 1.0 {
1099                    return RevealedOffset::new(f64::NEG_INFINITY, target_rect);
1100                }
1101                // If child's growth direction is reverse, when viewport.offset is
1102                // `leading_scroll_offset`, it is positioned just outside of the leading edge of
1103                // the viewport.
1104                leading_scroll_offset -= match axis {
1105                    Axis::Vertical => target_rect.height(),
1106                    Axis::Horizontal => target_rect.width(),
1107                };
1108            }
1109        }
1110
1111        let size = self.size(app);
1112        let main_axis_extent_difference = match axis {
1113            Axis::Horizontal => size.width() - extent_of_pinned_slivers - rect_local.width(),
1114            Axis::Vertical => size.height() - extent_of_pinned_slivers - rect_local.height(),
1115        };
1116
1117        let target_offset = leading_scroll_offset - main_axis_extent_difference * alignment;
1118        let offset_difference = self.offset(app).pixels(app) - target_offset;
1119
1120        target_rect = match self.axis_direction(app) {
1121            AxisDirection::Up => target_rect.translate(0.0, -offset_difference),
1122            AxisDirection::Down => target_rect.translate(0.0, offset_difference),
1123            AxisDirection::Left => target_rect.translate(-offset_difference, 0.0),
1124            AxisDirection::Right => target_rect.translate(offset_difference, 0.0),
1125        };
1126
1127        RevealedOffset::new(target_offset, target_rect)
1128    }
1129
1130    /// The offset at which the given `child` should be painted.
1131    ///
1132    /// The returned offset is from the top left corner of the inside of the viewport to the top
1133    /// left corner of the paint coordinate system of the `child`.
1134    ///
1135    /// See also:
1136    ///
1137    ///  * [`paint_offset_of`](Self::paint_offset_of), which uses the layout offset and growth
1138    ///    direction computed for the child during layout.
1139    fn compute_absolute_paint_offset(
1140        self: RenderHandle<Self>,
1141        app: &App,
1142        child: AnyRenderSliver,
1143        layout_offset: f64,
1144        growth_direction: GrowthDirection,
1145    ) -> Offset {
1146        // this is only usable once we have a size
1147        debug_assert!(self.has_size(app));
1148        let paint_extent = child.geometry(app).paint_extent;
1149        let size = self.size(app);
1150        match apply_growth_direction_to_axis_direction(self.axis_direction(app), growth_direction) {
1151            AxisDirection::Up => Offset::new(0.0, size.height() - layout_offset - paint_extent),
1152            AxisDirection::Left => Offset::new(size.width() - layout_offset - paint_extent, 0.0),
1153            AxisDirection::Right => Offset::new(layout_offset, 0.0),
1154            AxisDirection::Down => Offset::new(0.0, layout_offset),
1155        }
1156    }
1157
1158    // API TO BE IMPLEMENTED BY SUBCLASSES
1159
1160    /// Whether the contents of this viewport would paint outside the bounds of the viewport if
1161    /// [`paint`](Self::paint) did not clip.
1162    ///
1163    /// This property enables an optimization whereby [`paint`](Self::paint) can skip applying a
1164    /// clip if the contents of the viewport are known to paint entirely within its bounds.
1165    fn has_visual_overflow(self: RenderHandle<Self>, app: &App) -> bool;
1166
1167    /// Called during [`layout_child_sequence`](Self::layout_child_sequence) for each child.
1168    ///
1169    /// Typically used by implementors to update any out-of-band data, such as the max scroll
1170    /// extent, for each child.
1171    fn update_out_of_band_data(
1172        self: RenderHandle<Self>,
1173        app: &mut App,
1174        growth_direction: GrowthDirection,
1175        child_layout_geometry: SliverGeometry,
1176    );
1177
1178    /// Called during [`layout_child_sequence`](Self::layout_child_sequence) to store the layout
1179    /// offset for the given child.
1180    ///
1181    /// Different implementors use different representations for their children's layout offset
1182    /// (e.g., logical or physical coordinates). This function lets them transform the child's
1183    /// layout offset before storing it in the child's parent data.
1184    fn update_child_layout_offset(
1185        self: RenderHandle<Self>,
1186        app: &mut App,
1187        child: AnyRenderSliver,
1188        layout_offset: f64,
1189        growth_direction: GrowthDirection,
1190    );
1191
1192    /// The offset at which the given `child` should be painted.
1193    ///
1194    /// The returned offset is from the top left corner of the inside of the viewport to the top
1195    /// left corner of the paint coordinate system of the `child`.
1196    ///
1197    /// See also:
1198    ///
1199    ///  * [`compute_absolute_paint_offset`](Self::compute_absolute_paint_offset), which computes
1200    ///    the paint offset from an explicit layout offset and growth direction instead of using
1201    ///    the values computed for the child during layout.
1202    fn paint_offset_of(self: RenderHandle<Self>, app: &App, child: AnyRenderSliver) -> Offset;
1203
1204    /// Returns the scroll offset within the viewport for the given `scroll_offset_within_child`
1205    /// within the given `child`.
1206    ///
1207    /// The returned value is an estimate that assumes the slivers within the viewport do not
1208    /// change the layout extent in response to changes in their scroll offset.
1209    fn scroll_offset_of(
1210        self: RenderHandle<Self>,
1211        app: &App,
1212        child: AnyRenderSliver,
1213        scroll_offset_within_child: f64,
1214    ) -> f64;
1215
1216    /// Returns the total scroll obstruction extent of all slivers in the viewport before
1217    /// `child`.
1218    ///
1219    /// This is the extent by which the actual area in which content can scroll is reduced. For
1220    /// example, an app bar that is pinned at the top will reduce the area in which content can
1221    /// actually scroll by the height of the app bar.
1222    fn max_scroll_obstruction_extent_before(
1223        self: RenderHandle<Self>,
1224        app: &App,
1225        child: AnyRenderSliver,
1226    ) -> f64;
1227
1228    /// Converts the `parent_main_axis_position` into the child's coordinate system.
1229    ///
1230    /// The `parent_main_axis_position` is a distance from the top edge (for vertical viewports)
1231    /// or left edge (for horizontal viewports) of the viewport bounds. This describes a line,
1232    /// perpendicular to the viewport's main axis, heretofore known as the target line.
1233    ///
1234    /// The child's coordinate system's origin in the main axis is at the leading edge of the
1235    /// given child, as given by the child's [`SliverConstraints::axis_direction`] and
1236    /// [`SliverConstraints::growth_direction`].
1237    ///
1238    /// This method returns the distance from the leading edge of the given child to the target
1239    /// line described above.
1240    fn compute_child_main_axis_position(
1241        self: RenderHandle<Self>,
1242        app: &App,
1243        child: AnyRenderSliver,
1244        parent_main_axis_position: f64,
1245    ) -> f64;
1246
1247    /// The index of the first child of the viewport relative to the center child.
1248    ///
1249    /// For example, the center child has index zero and the first child in the reverse growth
1250    /// direction has index -1.
1251    fn index_of_first_child(self: RenderHandle<Self>, app: &App) -> i32;
1252
1253    /// A short string to identify the child with the given index.
1254    fn label_for_child(self: RenderHandle<Self>, index: i32) -> String;
1255
1256    /// Walks the children of the viewport, in the order that they should be painted.
1257    ///
1258    /// This is the reverse order of
1259    /// [`children_in_hit_test_order`](Self::children_in_hit_test_order).
1260    fn children_in_paint_order(self: RenderHandle<Self>, app: &App) -> Vec<AnyRenderSliver> {
1261        match self.paint_order(app) {
1262            SliverPaintOrder::FirstIsTop => self.children_last_to_first(app),
1263            SliverPaintOrder::LastIsTop => self.children_as_list(app),
1264        }
1265    }
1266
1267    /// Walks the children of the viewport, in the order that hit-testing should use.
1268    ///
1269    /// This is the reverse order of [`children_in_paint_order`](Self::children_in_paint_order).
1270    fn children_in_hit_test_order(self: RenderHandle<Self>, app: &App) -> Vec<AnyRenderSliver> {
1271        match self.paint_order(app) {
1272            SliverPaintOrder::FirstIsTop => self.children_as_list(app),
1273            SliverPaintOrder::LastIsTop => self.children_last_to_first(app),
1274        }
1275    }
1276
1277    /// Flutter's `_childrenLastToFirst`.
1278    fn children_last_to_first(self: RenderHandle<Self>, app: &App) -> Vec<AnyRenderSliver> {
1279        let mut children = Vec::new();
1280        let mut child = self.last_child(app);
1281        while let Some(current) = child {
1282            children.push(current);
1283            child = self.child_before(app, current);
1284        }
1285        children
1286    }
1287
1288    /// The body of Flutter's `showOnScreen` override.
1289    fn show_on_screen(
1290        self: RenderHandle<Self>,
1291        app: &mut App,
1292        descendant: Option<AnyRenderObject>,
1293        rect: Option<Rect>,
1294        duration: Duration,
1295        curve: Rc<dyn Curve>,
1296    ) {
1297        let offset = self.offset(app);
1298        if !offset.allow_implicit_scrolling(app) {
1299            return crate::object::RenderObjectBase::show_on_screen(
1300                self, app, descendant, rect, duration, curve,
1301            );
1302        }
1303
1304        let new_rect = show_in_viewport(
1305            app,
1306            descendant,
1307            rect,
1308            self.as_abstract_viewport(),
1309            offset,
1310            duration,
1311            Rc::clone(&curve),
1312        );
1313        crate::object::RenderObjectBase::show_on_screen(self, app, None, new_rect, duration, curve);
1314    }
1315}
1316
1317/// Dart's `_offset.addListener(markNeedsLayout)` tear-off body: a named function so that the
1318/// listener added on attach compares equal to the one removed on detach.
1319fn viewport_offset_changed<T: RenderViewportBase>(
1320    this: inset_foundation::Handle<T>,
1321    app: &mut App,
1322) {
1323    RenderHandle::from_handle(this).mark_needs_layout(app);
1324}
1325
1326/// Make (a portion of) the given `descendant` of the given `viewport` fully visible in the
1327/// `viewport` by manipulating the provided [`AnyViewportOffset`] `offset`.
1328///
1329/// The optional `rect` parameter describes which area of the `descendant` should be shown in the
1330/// viewport. If `rect` is `None`, the entire `descendant` will be revealed. The `rect` parameter
1331/// is interpreted relative to the coordinate system of `descendant`.
1332///
1333/// The returned [`Rect`] describes the new location of `descendant` or `rect` in the viewport
1334/// after it has been revealed. See [`RevealedOffset::rect`] for a full definition of this rect.
1335///
1336/// If `descendant` is `None`, this is a no-op and `rect` is returned.
1337///
1338/// The `duration` parameter can be set to a non-zero value to animate the target object into the
1339/// viewport with an animation defined by `curve`.
1340///
1341/// Flutter's `RenderViewportBase.showInViewport` static.
1342pub fn show_in_viewport(
1343    app: &mut App,
1344    descendant: Option<AnyRenderObject>,
1345    rect: Option<Rect>,
1346    viewport: AnyRenderAbstractViewport,
1347    offset: AnyViewportOffset,
1348    duration: Duration,
1349    curve: Rc<dyn Curve>,
1350) -> Option<Rect> {
1351    let Some(descendant) = descendant else {
1352        return rect;
1353    };
1354    let leading_edge_offset = viewport.get_offset_to_reveal(app, descendant, 0.0, rect, None);
1355    let trailing_edge_offset = viewport.get_offset_to_reveal(app, descendant, 1.0, rect, None);
1356    let current_offset = offset.pixels(app);
1357    let target_offset =
1358        RevealedOffset::clamp_offset(leading_edge_offset, trailing_edge_offset, current_offset);
1359    let Some(target_offset) = target_offset else {
1360        // `descendant` is between leading and trailing edge and hence already fully shown on
1361        // screen. No action necessary.
1362        let viewport_object = viewport.as_object(app);
1363        debug_assert!(viewport_object.parent(app).is_some());
1364        let transform = descendant.get_transform_to(app, viewport_object.parent(app));
1365        return Some(transform_rect(
1366            &transform,
1367            rect.unwrap_or_else(|| descendant.paint_bounds(app)),
1368        ));
1369    };
1370
1371    offset.move_to(app, target_offset.offset, Some(duration), Some(curve), None);
1372    Some(target_offset.rect)
1373}
1374
1375/// A render object that is bigger on the inside.
1376///
1377/// [`RenderViewport`] is the visual workhorse of the scrolling machinery. It displays a subset of
1378/// its children according to its own dimensions and the given
1379/// [`offset`](RenderViewportBase::offset). As the offset varies, different children are visible
1380/// through the viewport.
1381///
1382/// [`RenderViewport`] hosts a bidirectional list of slivers in a single shared [`Axis`], anchored
1383/// on a [`center`](Self::center) sliver, which is placed at the zero scroll offset. The center
1384/// widget is displayed in the viewport according to the [`anchor`](Self::anchor) property.
1385///
1386/// Slivers that are earlier in the child list than [`center`](Self::center) are displayed in
1387/// reverse order in the reverse [`RenderViewportBase::axis_direction`] starting from the center.
1388///
1389/// [`RenderViewport`] cannot contain [`RenderBox`] children directly. Instead, use a
1390/// [`crate::RenderSliverList`], [`crate::RenderSliverFixedExtentList`], or a
1391/// [`crate::RenderSliverToBoxAdapter`], for example.
1392pub struct RenderViewport {
1393    render_object: RenderObjectData,
1394    render_box: RenderBoxData,
1395    container: ContainerRenderObjectData<AnyRenderSliver>,
1396    viewport: RenderViewportBaseData,
1397    anchor: f64,
1398    center: Option<AnyRenderSliver>,
1399    // Out-of-band data computed during layout.
1400    min_scroll_extent: f64,
1401    max_scroll_extent: f64,
1402    has_visual_overflow: bool,
1403}
1404
1405impl RenderViewport {
1406    /// Dart's `_maxLayoutCyclesPerChild`.
1407    const MAX_LAYOUT_CYCLES_PER_CHILD: usize = 10;
1408
1409    /// Creates a viewport for [`AnyRenderSliver`] objects.
1410    ///
1411    /// If the `center` is not specified, then the first child in the `children` list, if any, is
1412    /// used.
1413    ///
1414    /// For testing purposes, consider passing a [`crate::FixedViewportOffset`] as the `offset`.
1415    pub fn new(
1416        app: &mut App,
1417        cross_axis_direction: AxisDirection,
1418        offset: AnyViewportOffset,
1419        children: Option<Vec<AnyRenderSliver>>,
1420        center: Option<AnyRenderSliver>,
1421    ) -> RenderHandle<RenderViewport> {
1422        let this = RenderHandle::new_box(
1423            app,
1424            RenderViewport {
1425                render_object: RenderObjectData::new(),
1426                render_box: RenderBoxData::new(),
1427                container: ContainerRenderObjectData::new(),
1428                viewport: RenderViewportBaseData::new(cross_axis_direction, offset),
1429                anchor: 0.0,
1430                center,
1431                min_scroll_extent: 0.0,
1432                max_scroll_extent: 0.0,
1433                has_visual_overflow: false,
1434            },
1435        );
1436        this.add_all(app, children);
1437        if center.is_none()
1438            && let Some(first_child) = this.first_child(app)
1439        {
1440            this.get_mut(app).center = Some(first_child);
1441        }
1442        this
1443    }
1444
1445    /// The relative position of the zero scroll offset.
1446    ///
1447    /// For example, if the [`RenderViewportBase::axis_direction`] is [`AxisDirection::Down`] or
1448    /// [`AxisDirection::Up`] and the anchor is 0.5, then the zero scroll offset is vertically
1449    /// centered within the viewport. If the anchor is 1.0, and the axis direction is
1450    /// [`AxisDirection::Right`], then the zero scroll offset is on the left edge of the viewport.
1451    pub fn anchor(self: RenderHandle<Self>, app: &App) -> f64 {
1452        self.get(app).anchor
1453    }
1454
1455    /// Sets [`anchor`](Self::anchor).
1456    pub fn set_anchor(self: RenderHandle<Self>, app: &mut App, value: f64) {
1457        debug_assert!((0.0..=1.0).contains(&value));
1458        if value == self.get(app).anchor {
1459            return;
1460        }
1461        self.get_mut(app).anchor = value;
1462        self.mark_needs_layout(app);
1463    }
1464
1465    /// The first child in the [`GrowthDirection::Forward`] growth direction.
1466    ///
1467    /// This child will be at the position defined by [`anchor`](Self::anchor) when the
1468    /// [`crate::ViewportOffset::pixels`] of [`RenderViewportBase::offset`] is `0`.
1469    ///
1470    /// Children after the center will be placed in the [`RenderViewportBase::axis_direction`]
1471    /// relative to the center. Children before it will be placed in the opposite direction, and
1472    /// will have a growth direction of [`GrowthDirection::Reverse`].
1473    ///
1474    /// The center must be a direct child of the viewport.
1475    pub fn center(self: RenderHandle<Self>, app: &App) -> Option<AnyRenderSliver> {
1476        self.get(app).center
1477    }
1478
1479    /// Sets [`center`](Self::center).
1480    pub fn set_center(self: RenderHandle<Self>, app: &mut App, value: Option<AnyRenderSliver>) {
1481        if value == self.get(app).center {
1482            return;
1483        }
1484        self.get_mut(app).center = value;
1485        self.mark_needs_layout(app);
1486    }
1487
1488    /// Flutter's `_attemptLayout`.
1489    fn attempt_layout(
1490        self: RenderHandle<Self>,
1491        app: &mut App,
1492        main_axis_extent: f64,
1493        cross_axis_extent: f64,
1494        corrected_offset: f64,
1495    ) -> f64 {
1496        debug_assert!(!main_axis_extent.is_nan());
1497        debug_assert!(main_axis_extent >= 0.0);
1498        debug_assert!(main_axis_extent.is_finite());
1499        debug_assert!(cross_axis_extent.is_finite());
1500        debug_assert!(cross_axis_extent >= 0.0);
1501        debug_assert!(corrected_offset.is_finite());
1502        let this = self.get_mut(app);
1503        this.min_scroll_extent = 0.0;
1504        this.max_scroll_extent = 0.0;
1505        this.has_visual_overflow = false;
1506
1507        // center_offset is the offset from the leading edge of the viewport to the zero scroll
1508        // offset (the line between the forward slivers and the reverse slivers).
1509        let anchor = self.get(app).anchor;
1510        let center_offset = main_axis_extent * anchor - corrected_offset;
1511        let reverse_direction_remaining_paint_extent =
1512            clamp_double(center_offset, 0.0, main_axis_extent);
1513        let forward_direction_remaining_paint_extent =
1514            clamp_double(main_axis_extent - center_offset, 0.0, main_axis_extent);
1515
1516        let calculated_cache_extent = self
1517            .scroll_cache_extent(app)
1518            .calculate_cache_offset(main_axis_extent);
1519        self.viewport_data_mut(app).calculated_cache_extent = Some(calculated_cache_extent);
1520
1521        let full_cache_extent = main_axis_extent + 2.0 * calculated_cache_extent;
1522        let center_cache_offset = center_offset + calculated_cache_extent;
1523        let reverse_direction_remaining_cache_extent =
1524            clamp_double(center_cache_offset, 0.0, full_cache_extent);
1525        let forward_direction_remaining_cache_extent = clamp_double(
1526            full_cache_extent - center_cache_offset,
1527            0.0,
1528            full_cache_extent,
1529        );
1530
1531        let center = self.center(app).expect("checked by perform_layout");
1532        let leading_negative_child = self.child_before(app, center);
1533
1534        if let Some(leading_negative_child) = leading_negative_child {
1535            // negative scroll offsets
1536            let result = self.layout_child_sequence(
1537                app,
1538                Some(leading_negative_child),
1539                main_axis_extent.max(center_offset) - main_axis_extent,
1540                0.0,
1541                forward_direction_remaining_paint_extent,
1542                reverse_direction_remaining_paint_extent,
1543                main_axis_extent,
1544                cross_axis_extent,
1545                GrowthDirection::Reverse,
1546                &|this, app, child| this.child_before(app, child),
1547                reverse_direction_remaining_cache_extent,
1548                clamp_double(
1549                    main_axis_extent - center_offset,
1550                    -calculated_cache_extent,
1551                    0.0,
1552                ),
1553            );
1554            if result != 0.0 {
1555                return -result;
1556            }
1557        }
1558
1559        // positive scroll offsets
1560        self.layout_child_sequence(
1561            app,
1562            Some(center),
1563            (-center_offset).max(0.0),
1564            if leading_negative_child.is_none() {
1565                (-center_offset).min(0.0)
1566            } else {
1567                0.0
1568            },
1569            if center_offset >= main_axis_extent {
1570                center_offset
1571            } else {
1572                reverse_direction_remaining_paint_extent
1573            },
1574            forward_direction_remaining_paint_extent,
1575            main_axis_extent,
1576            cross_axis_extent,
1577            GrowthDirection::Forward,
1578            &|this, app, child| this.child_after(app, child),
1579            forward_direction_remaining_cache_extent,
1580            clamp_double(center_offset, -calculated_cache_extent, 0.0),
1581        )
1582    }
1583}
1584
1585impl ContainerRenderObjectMixin for RenderViewport {
1586    type ChildType = AnyRenderSliver;
1587    type ParentDataType = SliverPhysicalContainerParentData;
1588
1589    fn container_data(
1590        self: RenderHandle<Self>,
1591        app: &App,
1592    ) -> &ContainerRenderObjectData<AnyRenderSliver> {
1593        &self.get(app).container
1594    }
1595
1596    fn container_data_mut(
1597        self: RenderHandle<Self>,
1598        app: &mut App,
1599    ) -> &mut ContainerRenderObjectData<AnyRenderSliver> {
1600        &mut self.get_mut(app).container
1601    }
1602}
1603
1604impl RenderViewportBase for RenderViewport {
1605    crate::render_viewport_base_accessors!();
1606
1607    fn has_visual_overflow(self: RenderHandle<Self>, app: &App) -> bool {
1608        self.get(app).has_visual_overflow
1609    }
1610
1611    fn update_out_of_band_data(
1612        self: RenderHandle<Self>,
1613        app: &mut App,
1614        growth_direction: GrowthDirection,
1615        child_layout_geometry: SliverGeometry,
1616    ) {
1617        let this = self.get_mut(app);
1618        match growth_direction {
1619            GrowthDirection::Forward => {
1620                this.max_scroll_extent += child_layout_geometry.scroll_extent
1621            }
1622            GrowthDirection::Reverse => {
1623                this.min_scroll_extent -= child_layout_geometry.scroll_extent
1624            }
1625        }
1626        if child_layout_geometry.has_visual_overflow {
1627            this.has_visual_overflow = true;
1628        }
1629    }
1630
1631    fn update_child_layout_offset(
1632        self: RenderHandle<Self>,
1633        app: &mut App,
1634        child: AnyRenderSliver,
1635        layout_offset: f64,
1636        growth_direction: GrowthDirection,
1637    ) {
1638        let paint_offset =
1639            self.compute_absolute_paint_offset(app, child, layout_offset, growth_direction);
1640        child
1641            .as_object()
1642            .parent_data_of_mut::<SliverPhysicalContainerParentData>(app)
1643            .set_paint_offset(paint_offset);
1644    }
1645
1646    fn paint_offset_of(self: RenderHandle<Self>, app: &App, child: AnyRenderSliver) -> Offset {
1647        child
1648            .as_object()
1649            .parent_data_of::<SliverPhysicalContainerParentData>(app)
1650            .paint_offset()
1651    }
1652
1653    fn scroll_offset_of(
1654        self: RenderHandle<Self>,
1655        app: &App,
1656        child: AnyRenderSliver,
1657        scroll_offset_within_child: f64,
1658    ) -> f64 {
1659        debug_assert_eq!(child.as_object().parent(app), Some(self.as_object()));
1660        match child.constraints(app).growth_direction {
1661            GrowthDirection::Forward => {
1662                let mut scroll_offset_to_child = 0.0;
1663                let mut current = self.center(app);
1664                while current != Some(child) {
1665                    let sliver = current.expect("the center precedes a forward child");
1666                    scroll_offset_to_child += sliver.geometry(app).scroll_extent;
1667                    current = self.child_after(app, sliver);
1668                }
1669                scroll_offset_to_child + scroll_offset_within_child
1670            }
1671            GrowthDirection::Reverse => {
1672                let mut scroll_offset_to_child = 0.0;
1673                let center = self.center(app).expect("a laid-out viewport has a center");
1674                let mut current = self.child_before(app, center);
1675                while current != Some(child) {
1676                    let sliver = current.expect("a reverse child precedes the center");
1677                    scroll_offset_to_child -= sliver.geometry(app).scroll_extent;
1678                    current = self.child_before(app, sliver);
1679                }
1680                scroll_offset_to_child - scroll_offset_within_child
1681            }
1682        }
1683    }
1684
1685    fn max_scroll_obstruction_extent_before(
1686        self: RenderHandle<Self>,
1687        app: &App,
1688        child: AnyRenderSliver,
1689    ) -> f64 {
1690        debug_assert_eq!(child.as_object().parent(app), Some(self.as_object()));
1691        let mut pinned_extent = 0.0;
1692        match child.constraints(app).growth_direction {
1693            GrowthDirection::Forward => {
1694                let mut current = self.center(app);
1695                while current != Some(child) {
1696                    let sliver = current.expect("the center precedes a forward child");
1697                    pinned_extent += sliver.geometry(app).max_scroll_obstruction_extent;
1698                    current = self.child_after(app, sliver);
1699                }
1700            }
1701            GrowthDirection::Reverse => {
1702                let center = self.center(app).expect("a laid-out viewport has a center");
1703                let mut current = self.child_before(app, center);
1704                while current != Some(child) {
1705                    let sliver = current.expect("a reverse child precedes the center");
1706                    pinned_extent += sliver.geometry(app).max_scroll_obstruction_extent;
1707                    current = self.child_before(app, sliver);
1708                }
1709            }
1710        }
1711        pinned_extent
1712    }
1713
1714    fn compute_child_main_axis_position(
1715        self: RenderHandle<Self>,
1716        app: &App,
1717        child: AnyRenderSliver,
1718        parent_main_axis_position: f64,
1719    ) -> f64 {
1720        let paint_offset = child
1721            .as_object()
1722            .parent_data_of::<SliverPhysicalContainerParentData>(app)
1723            .paint_offset();
1724        let constraints = child.constraints(app);
1725        let paint_extent = child.geometry(app).paint_extent;
1726        match apply_growth_direction_to_axis_direction(
1727            constraints.axis_direction,
1728            constraints.growth_direction,
1729        ) {
1730            AxisDirection::Down => parent_main_axis_position - paint_offset.dy(),
1731            AxisDirection::Right => parent_main_axis_position - paint_offset.dx(),
1732            AxisDirection::Up => paint_extent - (parent_main_axis_position - paint_offset.dy()),
1733            AxisDirection::Left => paint_extent - (parent_main_axis_position - paint_offset.dx()),
1734        }
1735    }
1736
1737    fn index_of_first_child(self: RenderHandle<Self>, app: &App) -> i32 {
1738        let center = self.center(app).expect("a laid-out viewport has a center");
1739        debug_assert_eq!(center.as_object().parent(app), Some(self.as_object()));
1740        debug_assert!(self.first_child(app).is_some());
1741        let mut count = 0;
1742        let mut child = Some(center);
1743        while child != self.first_child(app) {
1744            count -= 1;
1745            child = self.child_before(app, child.expect("the first child terminates the walk"));
1746        }
1747        count
1748    }
1749
1750    fn label_for_child(self: RenderHandle<Self>, index: i32) -> String {
1751        if index == 0 {
1752            "center child".to_owned()
1753        } else {
1754            format!("child {index}")
1755        }
1756    }
1757}
1758
1759impl RenderAbstractViewport for RenderViewport {
1760    fn get_offset_to_reveal(
1761        self: RenderHandle<Self>,
1762        app: &App,
1763        target: AnyRenderObject,
1764        alignment: f64,
1765        rect: Option<Rect>,
1766        axis: Option<Axis>,
1767    ) -> RevealedOffset {
1768        RenderViewportBase::get_offset_to_reveal(self, app, target, alignment, rect, axis)
1769    }
1770}
1771
1772impl RenderObject for RenderViewport {
1773    crate::render_object_accessors!();
1774
1775    fn visit_children(
1776        self: RenderHandle<Self>,
1777        app: &App,
1778        visitor: &mut dyn FnMut(AnyRenderObject),
1779    ) {
1780        ContainerRenderObjectMixin::visit_children(self, app, visitor)
1781    }
1782
1783    fn did_attach(
1784        self: RenderHandle<Self>,
1785        app: &mut App,
1786        owner: inset_foundation::Handle<PipelineOwner>,
1787    ) {
1788        RenderViewportBase::did_attach(self, app, owner)
1789    }
1790
1791    fn did_detach(self: RenderHandle<Self>, app: &mut App) {
1792        RenderViewportBase::did_detach(self, app)
1793    }
1794
1795    fn redepth_children(self: RenderHandle<Self>, app: &mut App) {
1796        ContainerRenderObjectMixin::redepth_children(self, app)
1797    }
1798
1799    fn is_repaint_boundary(self: RenderHandle<Self>, _app: &App) -> bool {
1800        let _ = self;
1801        true
1802    }
1803
1804    fn interface(self: RenderHandle<Self>, id: TypeId) -> Option<Box<dyn Any>> {
1805        (id == TypeId::of::<AnyRenderAbstractViewport>())
1806            .then(|| Box::new(self.as_abstract_viewport()) as Box<dyn Any>)
1807    }
1808
1809    fn sized_by_parent(self: RenderHandle<Self>, _app: &App) -> bool {
1810        let _ = self;
1811        true
1812    }
1813
1814    fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
1815        // Ignore the return value of apply_viewport_dimension because we are doing a layout
1816        // regardless.
1817        let offset = self.offset(app);
1818        let size = self.size(app);
1819        match self.axis(app) {
1820            Axis::Vertical => offset.apply_viewport_dimension(app, size.height()),
1821            Axis::Horizontal => offset.apply_viewport_dimension(app, size.width()),
1822        };
1823
1824        let Some(center) = self.center(app) else {
1825            debug_assert!(self.first_child(app).is_none());
1826            let this = self.get_mut(app);
1827            this.min_scroll_extent = 0.0;
1828            this.max_scroll_extent = 0.0;
1829            this.has_visual_overflow = false;
1830            offset.apply_content_dimensions(app, 0.0, 0.0);
1831            return;
1832        };
1833        debug_assert_eq!(center.as_object().parent(app), Some(self.as_object()));
1834
1835        let (main_axis_extent, cross_axis_extent) = match self.axis(app) {
1836            Axis::Vertical => (size.height(), size.width()),
1837            Axis::Horizontal => (size.width(), size.height()),
1838        };
1839
1840        let center_offset_adjustment = center.center_offset_adjustment(app);
1841        let max_layout_cycles = RenderViewport::MAX_LAYOUT_CYCLES_PER_CHILD * self.child_count(app);
1842
1843        let anchor = self.get(app).anchor;
1844        let mut count = 0;
1845        loop {
1846            let correction = self.attempt_layout(
1847                app,
1848                main_axis_extent,
1849                cross_axis_extent,
1850                offset.pixels(app) + center_offset_adjustment,
1851            );
1852            if correction != 0.0 {
1853                offset.correct_by(app, correction);
1854            } else {
1855                let min_scroll_extent = self.get(app).min_scroll_extent;
1856                let max_scroll_extent = self.get(app).max_scroll_extent;
1857                if offset.apply_content_dimensions(
1858                    app,
1859                    (min_scroll_extent + main_axis_extent * anchor).min(0.0),
1860                    (max_scroll_extent - main_axis_extent * (1.0 - anchor)).max(0.0),
1861                ) {
1862                    break;
1863                }
1864            }
1865            count += 1;
1866            if count >= max_layout_cycles {
1867                break;
1868            }
1869        }
1870        debug_assert!(
1871            count < max_layout_cycles,
1872            "A RenderViewport exceeded its maximum number of layout cycles.\n\
1873             RenderViewport render objects, during layout, can retry if either their slivers or \
1874             their ViewportOffset decide that the offset should be corrected to take into \
1875             account information collected during that layout.\n\
1876             In the case of this RenderViewport object, however, this happened {count} times and \
1877             still there was no consensus on the scroll offset. This usually indicates a bug."
1878        );
1879    }
1880
1881    fn paint(
1882        self: RenderHandle<Self>,
1883        app: &mut App,
1884        context: &mut PaintingContext,
1885        offset: Offset,
1886    ) {
1887        RenderViewportBase::paint(self, app, context, offset)
1888    }
1889
1890    fn dispose(self: RenderHandle<Self>, app: &mut App) {
1891        RenderViewportBase::dispose(self, app)
1892    }
1893
1894    fn show_on_screen(
1895        self: RenderHandle<Self>,
1896        app: &mut App,
1897        descendant: Option<AnyRenderObject>,
1898        rect: Option<Rect>,
1899        duration: Duration,
1900        curve: Rc<dyn Curve>,
1901    ) {
1902        RenderViewportBase::show_on_screen(self, app, descendant, rect, duration, curve)
1903    }
1904}
1905
1906impl RenderBox for RenderViewport {
1907    crate::render_box_accessors!();
1908
1909    fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
1910        if !child.parent_data_is::<SliverPhysicalContainerParentData>(app) {
1911            child.set_parent_data(app, SliverPhysicalContainerParentData::new());
1912        }
1913    }
1914
1915    fn apply_paint_transform(
1916        self: RenderHandle<Self>,
1917        app: &App,
1918        child: AnyRenderObject,
1919        transform: &mut Matrix4,
1920    ) {
1921        // Hit test logic relies on this always providing an invertible matrix.
1922        child
1923            .parent_data_of::<SliverPhysicalContainerParentData>(app)
1924            .apply_paint_transform(transform);
1925    }
1926
1927    fn hit_test_children(
1928        self: RenderHandle<Self>,
1929        app: &mut App,
1930        result: &mut BoxHitTestResult<'_>,
1931        position: Offset,
1932    ) -> bool {
1933        RenderViewportBase::hit_test_children(self, app, result, position)
1934    }
1935
1936    fn compute_min_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
1937        RenderViewportBase::compute_min_intrinsic_width(self, app, height)
1938    }
1939
1940    fn compute_max_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
1941        RenderViewportBase::compute_max_intrinsic_width(self, app, height)
1942    }
1943
1944    fn compute_min_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
1945        RenderViewportBase::compute_min_intrinsic_height(self, app, width)
1946    }
1947
1948    fn compute_max_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
1949        RenderViewportBase::compute_max_intrinsic_height(self, app, width)
1950    }
1951
1952    fn compute_dry_layout(
1953        self: RenderHandle<Self>,
1954        app: &mut App,
1955        constraints: BoxConstraints,
1956    ) -> Size {
1957        debug_assert!(debug_check_has_bounded_axis(self.axis(app), constraints));
1958        constraints.biggest()
1959    }
1960}
1961
1962/// A render object that is bigger on the inside and shrink wraps its children in the main axis.
1963///
1964/// [`RenderShrinkWrappingViewport`] displays a subset of its children according to its own
1965/// dimensions and the given [`RenderViewportBase::offset`]. As the offset varies, different
1966/// children are visible through the viewport.
1967///
1968/// It differs from [`RenderViewport`] in that [`RenderViewport`] expands to fill the main axis
1969/// whereas this shrink-wraps itself to match its children in the main axis. This shrink wrapping
1970/// behavior is expensive because the children, and hence the viewport, could potentially change
1971/// size whenever the offset changes (e.g., because of a collapsing header).
1972///
1973/// [`RenderShrinkWrappingViewport`] cannot contain [`RenderBox`] children directly. Instead, use
1974/// a [`crate::RenderSliverList`], [`crate::RenderSliverFixedExtentList`], or a
1975/// [`crate::RenderSliverToBoxAdapter`], for example.
1976pub struct RenderShrinkWrappingViewport {
1977    render_object: RenderObjectData,
1978    render_box: RenderBoxData,
1979    container: ContainerRenderObjectData<AnyRenderSliver>,
1980    viewport: RenderViewportBaseData,
1981    // Out-of-band data computed during layout.
1982    max_scroll_extent: f64,
1983    shrink_wrap_extent: f64,
1984    has_visual_overflow: bool,
1985}
1986
1987impl RenderShrinkWrappingViewport {
1988    /// Creates a viewport (for [`AnyRenderSliver`] objects) that shrink-wraps its contents.
1989    ///
1990    /// For testing purposes, consider passing a [`crate::FixedViewportOffset`] as the `offset`.
1991    pub fn new(
1992        app: &mut App,
1993        cross_axis_direction: AxisDirection,
1994        offset: AnyViewportOffset,
1995        children: Option<Vec<AnyRenderSliver>>,
1996    ) -> RenderHandle<RenderShrinkWrappingViewport> {
1997        let this = RenderHandle::new_box(
1998            app,
1999            RenderShrinkWrappingViewport {
2000                render_object: RenderObjectData::new(),
2001                render_box: RenderBoxData::new(),
2002                container: ContainerRenderObjectData::new(),
2003                viewport: RenderViewportBaseData::new(cross_axis_direction, offset),
2004                max_scroll_extent: 0.0,
2005                shrink_wrap_extent: 0.0,
2006                has_visual_overflow: false,
2007            },
2008        );
2009        this.add_all(app, children);
2010        this
2011    }
2012
2013    /// Flutter's `_debugCheckHasBoundedCrossAxis`.
2014    fn debug_check_has_bounded_cross_axis(self: RenderHandle<Self>, app: &App) -> bool {
2015        let constraints = self.constraints(app);
2016        match self.axis(app) {
2017            Axis::Vertical => debug_assert!(
2018                constraints.has_bounded_width(),
2019                "Vertical viewport was given unbounded width.\n\
2020                 Viewports expand in the cross axis to fill their container and constrain their \
2021                 children to match their extent in the cross axis. In this case, a vertical \
2022                 shrinkwrapping viewport was given an unlimited amount of horizontal space in \
2023                 which to expand."
2024            ),
2025            Axis::Horizontal => debug_assert!(
2026                constraints.has_bounded_height(),
2027                "Horizontal viewport was given unbounded height.\n\
2028                 Viewports expand in the cross axis to fill their container and constrain their \
2029                 children to match their extent in the cross axis. In this case, a horizontal \
2030                 shrinkwrapping viewport was given an unlimited amount of vertical space in \
2031                 which to expand."
2032            ),
2033        }
2034        true
2035    }
2036
2037    /// Flutter's `_attemptLayout`.
2038    fn attempt_layout(
2039        self: RenderHandle<Self>,
2040        app: &mut App,
2041        main_axis_extent: f64,
2042        cross_axis_extent: f64,
2043        corrected_offset: f64,
2044    ) -> f64 {
2045        // We can't assert main_axis_extent is finite, because it could be infinite if it is
2046        // within a column or row for example. In such a case, there's not even any scrolling to
2047        // do, although some scroll physics could still temporarily scroll the content in a
2048        // simulation.
2049        debug_assert!(!main_axis_extent.is_nan());
2050        debug_assert!(main_axis_extent >= 0.0);
2051        debug_assert!(cross_axis_extent.is_finite());
2052        debug_assert!(cross_axis_extent >= 0.0);
2053        debug_assert!(corrected_offset.is_finite());
2054        let this = self.get_mut(app);
2055        this.max_scroll_extent = 0.0;
2056        this.shrink_wrap_extent = 0.0;
2057        // Since the viewport is shrinkwrapped, we know that any negative overscroll into the
2058        // potentially infinite main_axis_extent will overflow the end of the viewport.
2059        this.has_visual_overflow = corrected_offset < 0.0;
2060        let calculated_cache_extent = if main_axis_extent.is_finite() {
2061            self.scroll_cache_extent(app)
2062                .calculate_cache_offset(main_axis_extent)
2063        } else {
2064            // If main_axis_extent is infinite, it builds everything anyway, so we don't need any
2065            // extra cache.
2066            0.0
2067        };
2068        self.viewport_data_mut(app).calculated_cache_extent = Some(calculated_cache_extent);
2069
2070        let first_child = self.first_child(app);
2071        self.layout_child_sequence(
2072            app,
2073            first_child,
2074            corrected_offset.max(0.0),
2075            corrected_offset.min(0.0),
2076            (-corrected_offset).max(0.0),
2077            main_axis_extent + corrected_offset.min(0.0),
2078            main_axis_extent,
2079            cross_axis_extent,
2080            GrowthDirection::Forward,
2081            &|this, app, child| this.child_after(app, child),
2082            main_axis_extent + 2.0 * calculated_cache_extent,
2083            -calculated_cache_extent,
2084        )
2085    }
2086}
2087
2088impl ContainerRenderObjectMixin for RenderShrinkWrappingViewport {
2089    type ChildType = AnyRenderSliver;
2090    type ParentDataType = SliverLogicalContainerParentData;
2091
2092    fn container_data(
2093        self: RenderHandle<Self>,
2094        app: &App,
2095    ) -> &ContainerRenderObjectData<AnyRenderSliver> {
2096        &self.get(app).container
2097    }
2098
2099    fn container_data_mut(
2100        self: RenderHandle<Self>,
2101        app: &mut App,
2102    ) -> &mut ContainerRenderObjectData<AnyRenderSliver> {
2103        &mut self.get_mut(app).container
2104    }
2105}
2106
2107impl RenderViewportBase for RenderShrinkWrappingViewport {
2108    crate::render_viewport_base_accessors!();
2109
2110    fn debug_throw_if_not_checking_intrinsics(self: RenderHandle<Self>) -> bool {
2111        let _ = self;
2112        debug_assert!(
2113            debug_checking_intrinsics(),
2114            "{} does not support returning intrinsic dimensions.\n\
2115             Calculating the intrinsic dimensions would require instantiating every child of \
2116             the viewport, which defeats the point of viewports being lazy.\n\
2117             If you are merely trying to shrink-wrap the viewport in the main axis direction, \
2118             you should be able to achieve that effect by just giving the viewport loose \
2119             constraints, without needing to measure its intrinsic dimensions.",
2120            std::any::type_name::<Self>()
2121        );
2122        true
2123    }
2124
2125    fn has_visual_overflow(self: RenderHandle<Self>, app: &App) -> bool {
2126        self.get(app).has_visual_overflow
2127    }
2128
2129    fn update_out_of_band_data(
2130        self: RenderHandle<Self>,
2131        app: &mut App,
2132        growth_direction: GrowthDirection,
2133        child_layout_geometry: SliverGeometry,
2134    ) {
2135        debug_assert_eq!(growth_direction, GrowthDirection::Forward);
2136        let this = self.get_mut(app);
2137        this.max_scroll_extent += child_layout_geometry.scroll_extent;
2138        if child_layout_geometry.has_visual_overflow {
2139            this.has_visual_overflow = true;
2140        }
2141        this.shrink_wrap_extent += child_layout_geometry.max_paint_extent;
2142    }
2143
2144    fn update_child_layout_offset(
2145        self: RenderHandle<Self>,
2146        app: &mut App,
2147        child: AnyRenderSliver,
2148        layout_offset: f64,
2149        growth_direction: GrowthDirection,
2150    ) {
2151        debug_assert_eq!(growth_direction, GrowthDirection::Forward);
2152        child
2153            .as_object()
2154            .parent_data_of_mut::<SliverLogicalContainerParentData>(app)
2155            .set_layout_offset(Some(layout_offset));
2156    }
2157
2158    fn paint_offset_of(self: RenderHandle<Self>, app: &App, child: AnyRenderSliver) -> Offset {
2159        let layout_offset = child
2160            .as_object()
2161            .parent_data_of::<SliverLogicalContainerParentData>(app)
2162            .layout_offset()
2163            .expect("a laid-out child has a layout offset");
2164        self.compute_absolute_paint_offset(app, child, layout_offset, GrowthDirection::Forward)
2165    }
2166
2167    fn scroll_offset_of(
2168        self: RenderHandle<Self>,
2169        app: &App,
2170        child: AnyRenderSliver,
2171        scroll_offset_within_child: f64,
2172    ) -> f64 {
2173        debug_assert_eq!(child.as_object().parent(app), Some(self.as_object()));
2174        debug_assert_eq!(
2175            child.constraints(app).growth_direction,
2176            GrowthDirection::Forward
2177        );
2178        let mut scroll_offset_to_child = 0.0;
2179        let mut current = self.first_child(app);
2180        while current != Some(child) {
2181            let sliver = current.expect("child is in the child list");
2182            scroll_offset_to_child += sliver.geometry(app).scroll_extent;
2183            current = self.child_after(app, sliver);
2184        }
2185        scroll_offset_to_child + scroll_offset_within_child
2186    }
2187
2188    fn max_scroll_obstruction_extent_before(
2189        self: RenderHandle<Self>,
2190        app: &App,
2191        child: AnyRenderSliver,
2192    ) -> f64 {
2193        debug_assert_eq!(child.as_object().parent(app), Some(self.as_object()));
2194        debug_assert_eq!(
2195            child.constraints(app).growth_direction,
2196            GrowthDirection::Forward
2197        );
2198        let mut pinned_extent = 0.0;
2199        let mut current = self.first_child(app);
2200        while current != Some(child) {
2201            let sliver = current.expect("child is in the child list");
2202            pinned_extent += sliver.geometry(app).max_scroll_obstruction_extent;
2203            current = self.child_after(app, sliver);
2204        }
2205        pinned_extent
2206    }
2207
2208    fn compute_child_main_axis_position(
2209        self: RenderHandle<Self>,
2210        app: &App,
2211        child: AnyRenderSliver,
2212        parent_main_axis_position: f64,
2213    ) -> f64 {
2214        debug_assert!(self.has_size(app));
2215        let layout_offset = child
2216            .as_object()
2217            .parent_data_of::<SliverLogicalContainerParentData>(app)
2218            .layout_offset()
2219            .expect("a laid-out child has a layout offset");
2220        let constraints = child.constraints(app);
2221        let size = self.size(app);
2222        match apply_growth_direction_to_axis_direction(
2223            constraints.axis_direction,
2224            constraints.growth_direction,
2225        ) {
2226            AxisDirection::Down | AxisDirection::Right => parent_main_axis_position - layout_offset,
2227            AxisDirection::Up => size.height() - parent_main_axis_position - layout_offset,
2228            AxisDirection::Left => size.width() - parent_main_axis_position - layout_offset,
2229        }
2230    }
2231
2232    fn index_of_first_child(self: RenderHandle<Self>, _app: &App) -> i32 {
2233        let _ = self;
2234        0
2235    }
2236
2237    fn label_for_child(self: RenderHandle<Self>, index: i32) -> String {
2238        let _ = self;
2239        format!("child {index}")
2240    }
2241}
2242
2243impl RenderAbstractViewport for RenderShrinkWrappingViewport {
2244    fn get_offset_to_reveal(
2245        self: RenderHandle<Self>,
2246        app: &App,
2247        target: AnyRenderObject,
2248        alignment: f64,
2249        rect: Option<Rect>,
2250        axis: Option<Axis>,
2251    ) -> RevealedOffset {
2252        RenderViewportBase::get_offset_to_reveal(self, app, target, alignment, rect, axis)
2253    }
2254}
2255
2256impl RenderObject for RenderShrinkWrappingViewport {
2257    crate::render_object_accessors!();
2258
2259    fn visit_children(
2260        self: RenderHandle<Self>,
2261        app: &App,
2262        visitor: &mut dyn FnMut(AnyRenderObject),
2263    ) {
2264        ContainerRenderObjectMixin::visit_children(self, app, visitor)
2265    }
2266
2267    fn did_attach(
2268        self: RenderHandle<Self>,
2269        app: &mut App,
2270        owner: inset_foundation::Handle<PipelineOwner>,
2271    ) {
2272        RenderViewportBase::did_attach(self, app, owner)
2273    }
2274
2275    fn did_detach(self: RenderHandle<Self>, app: &mut App) {
2276        RenderViewportBase::did_detach(self, app)
2277    }
2278
2279    fn redepth_children(self: RenderHandle<Self>, app: &mut App) {
2280        ContainerRenderObjectMixin::redepth_children(self, app)
2281    }
2282
2283    fn is_repaint_boundary(self: RenderHandle<Self>, _app: &App) -> bool {
2284        let _ = self;
2285        true
2286    }
2287
2288    fn interface(self: RenderHandle<Self>, id: TypeId) -> Option<Box<dyn Any>> {
2289        (id == TypeId::of::<AnyRenderAbstractViewport>())
2290            .then(|| Box::new(self.as_abstract_viewport()) as Box<dyn Any>)
2291    }
2292
2293    fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
2294        let constraints = self.constraints(app);
2295        let offset = self.offset(app);
2296        if self.first_child(app).is_none() {
2297            // Shrinkwrapping viewport only requires the cross axis to be bounded.
2298            debug_assert!(self.debug_check_has_bounded_cross_axis(app));
2299            let size = match self.axis(app) {
2300                Axis::Vertical => Size::new(constraints.max_width, constraints.min_height),
2301                Axis::Horizontal => Size::new(constraints.min_width, constraints.max_height),
2302            };
2303            self.set_size(app, size);
2304            offset.apply_viewport_dimension(app, 0.0);
2305            let this = self.get_mut(app);
2306            this.max_scroll_extent = 0.0;
2307            this.shrink_wrap_extent = 0.0;
2308            this.has_visual_overflow = false;
2309            offset.apply_content_dimensions(app, 0.0, 0.0);
2310            return;
2311        }
2312
2313        // Shrinkwrapping viewport only requires the cross axis to be bounded.
2314        debug_assert!(self.debug_check_has_bounded_cross_axis(app));
2315        let (main_axis_extent, cross_axis_extent) = match self.axis(app) {
2316            Axis::Vertical => (constraints.max_height, constraints.max_width),
2317            Axis::Horizontal => (constraints.max_width, constraints.max_height),
2318        };
2319
2320        let effective_extent;
2321        loop {
2322            let corrected_offset = offset.pixels(app);
2323            let correction =
2324                self.attempt_layout(app, main_axis_extent, cross_axis_extent, corrected_offset);
2325            if correction != 0.0 {
2326                offset.correct_by(app, correction);
2327                continue;
2328            }
2329            let shrink_wrap_extent = self.get(app).shrink_wrap_extent;
2330            let extent = match self.axis(app) {
2331                Axis::Vertical => constraints.constrain_height(shrink_wrap_extent),
2332                Axis::Horizontal => constraints.constrain_width(shrink_wrap_extent),
2333            };
2334            let did_accept_viewport_dimension = offset.apply_viewport_dimension(app, extent);
2335            let max_scroll_extent = self.get(app).max_scroll_extent;
2336            let did_accept_content_dimension =
2337                offset.apply_content_dimensions(app, 0.0, (max_scroll_extent - extent).max(0.0));
2338            if did_accept_viewport_dimension && did_accept_content_dimension {
2339                effective_extent = extent;
2340                break;
2341            }
2342        }
2343        let size = match self.axis(app) {
2344            Axis::Vertical => constraints.constrain_dimensions(cross_axis_extent, effective_extent),
2345            Axis::Horizontal => {
2346                constraints.constrain_dimensions(effective_extent, cross_axis_extent)
2347            }
2348        };
2349        self.set_size(app, size);
2350    }
2351
2352    fn paint(
2353        self: RenderHandle<Self>,
2354        app: &mut App,
2355        context: &mut PaintingContext,
2356        offset: Offset,
2357    ) {
2358        RenderViewportBase::paint(self, app, context, offset)
2359    }
2360
2361    fn dispose(self: RenderHandle<Self>, app: &mut App) {
2362        RenderViewportBase::dispose(self, app)
2363    }
2364
2365    fn show_on_screen(
2366        self: RenderHandle<Self>,
2367        app: &mut App,
2368        descendant: Option<AnyRenderObject>,
2369        rect: Option<Rect>,
2370        duration: Duration,
2371        curve: Rc<dyn Curve>,
2372    ) {
2373        RenderViewportBase::show_on_screen(self, app, descendant, rect, duration, curve)
2374    }
2375}
2376
2377impl RenderBox for RenderShrinkWrappingViewport {
2378    crate::render_box_accessors!();
2379
2380    fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
2381        if !child.parent_data_is::<SliverLogicalContainerParentData>(app) {
2382            child.set_parent_data(app, SliverLogicalContainerParentData::new());
2383        }
2384    }
2385
2386    fn apply_paint_transform(
2387        self: RenderHandle<Self>,
2388        app: &App,
2389        child: AnyRenderObject,
2390        transform: &mut Matrix4,
2391    ) {
2392        // Hit test logic relies on this always providing an invertible matrix.
2393        let offset = self.paint_offset_of(
2394            app,
2395            child
2396                .as_sliver()
2397                .expect("a viewport's children are slivers"),
2398        );
2399        translate(transform, offset.dx(), offset.dy());
2400    }
2401
2402    fn hit_test_children(
2403        self: RenderHandle<Self>,
2404        app: &mut App,
2405        result: &mut BoxHitTestResult<'_>,
2406        position: Offset,
2407    ) -> bool {
2408        RenderViewportBase::hit_test_children(self, app, result, position)
2409    }
2410
2411    fn compute_min_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
2412        RenderViewportBase::compute_min_intrinsic_width(self, app, height)
2413    }
2414
2415    fn compute_max_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
2416        RenderViewportBase::compute_max_intrinsic_width(self, app, height)
2417    }
2418
2419    fn compute_min_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
2420        RenderViewportBase::compute_min_intrinsic_height(self, app, width)
2421    }
2422
2423    fn compute_max_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
2424        RenderViewportBase::compute_max_intrinsic_height(self, app, width)
2425    }
2426}
2427
2428#[cfg(test)]
2429mod tests {
2430    use inset_foundation::AppCell;
2431    use std::cell::Cell;
2432    use std::rc::Rc;
2433
2434    use inset_foundation::Handle;
2435    use inset_gestures::HitTestResult;
2436
2437    use super::*;
2438    use crate::box_::BoxConstraints;
2439    use crate::layer::{ContainerLayer, ErasedLayer, OffsetLayer};
2440    use crate::proxy_box::RenderConstrainedBox;
2441    use crate::sliver::{RenderSliver, RenderSliverToBoxAdapter};
2442    use crate::viewport_offset::{FixedViewportOffset, ViewportOffset};
2443
2444    /// A sliver of a fixed scroll extent that records the offsets it was painted at.
2445    struct TestSliver {
2446        render_object: RenderObjectData,
2447        render_sliver: crate::sliver::RenderSliverData,
2448        extent: f64,
2449        paint_offsets: Rc<Cell<Vec<Offset>>>,
2450    }
2451
2452    impl RenderObject for TestSliver {
2453        crate::render_object_accessors!();
2454
2455        fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
2456            let constraints = self.constraints(app);
2457            let extent = self.get(app).extent;
2458            let painted = self.calculate_paint_offset(app, constraints, 0.0, extent);
2459            let cached = self.calculate_cache_offset(app, constraints, 0.0, extent);
2460            self.set_geometry(
2461                app,
2462                SliverGeometry::builder()
2463                    .scroll_extent(extent)
2464                    .paint_extent(painted)
2465                    .cache_extent(cached)
2466                    .max_paint_extent(extent)
2467                    .build(),
2468            );
2469        }
2470
2471        fn paint(
2472            self: RenderHandle<Self>,
2473            app: &mut App,
2474            _context: &mut PaintingContext,
2475            offset: Offset,
2476        ) {
2477            let recorded = self.get(app).paint_offsets.clone();
2478            let mut offsets = recorded.take();
2479            offsets.push(offset);
2480            recorded.set(offsets);
2481        }
2482    }
2483
2484    impl RenderSliver for TestSliver {
2485        crate::render_sliver_accessors!();
2486
2487        fn hit_test_self(
2488            self: RenderHandle<Self>,
2489            _app: &App,
2490            _main_axis_position: f64,
2491            _cross_axis_position: f64,
2492        ) -> bool {
2493            let _ = self;
2494            true
2495        }
2496    }
2497
2498    fn test_sliver(app: &mut App, extent: f64) -> (AnyRenderSliver, Rc<Cell<Vec<Offset>>>) {
2499        let paint_offsets = Rc::new(Cell::new(Vec::new()));
2500        let sliver = RenderHandle::new_sliver(
2501            app,
2502            TestSliver {
2503                render_object: RenderObjectData::new(),
2504                render_sliver: crate::sliver::RenderSliverData::new(),
2505                extent,
2506                paint_offsets: paint_offsets.clone(),
2507            },
2508        )
2509        .as_sliver();
2510        (sliver, paint_offsets)
2511    }
2512
2513    /// The test binding's first frame: root the tree, lay it out, and flush paint.
2514    fn first_frame(
2515        app: &mut App,
2516        root: crate::box_::AnyRenderBox,
2517        constraints: BoxConstraints,
2518    ) -> Handle<PipelineOwner> {
2519        let owner = PipelineOwner::new(app, None, None);
2520        owner.set_root_node(app, Some(root.as_object()));
2521        root.layout(app, constraints, false);
2522        let paint_root = OffsetLayer::new(app, Offset::ZERO);
2523        paint_root.as_layer().attach(app, root.as_object().id());
2524        root.as_object()
2525            .schedule_initial_paint(app, paint_root.as_container_layer());
2526        owner.flush_compositing_bits(app);
2527        owner.flush_paint(app);
2528        owner
2529    }
2530
2531    fn vertical_viewport(
2532        app: &mut App,
2533        offset: Handle<FixedViewportOffset>,
2534        children: Vec<AnyRenderSliver>,
2535    ) -> RenderHandle<RenderViewport> {
2536        RenderViewport::new(
2537            app,
2538            AxisDirection::Right,
2539            offset.as_viewport_offset(),
2540            Some(children),
2541            None,
2542        )
2543    }
2544
2545    /// `viewport_test.dart`: a viewport does not support intrinsics, because answering would
2546    /// mean instantiating every child.
2547    #[test]
2548    #[should_panic(expected = "does not support returning intrinsic dimensions")]
2549    fn a_viewport_has_no_intrinsic_dimensions() {
2550        let cell = AppCell::new();
2551        let mut app = cell.borrow_mut();
2552        let offset = FixedViewportOffset::zero(&mut app);
2553        let viewport = vertical_viewport(&mut app, offset, vec![]);
2554        viewport.as_box().get_min_intrinsic_width(&mut app, 0.0);
2555    }
2556
2557    /// While the framework is checking intrinsics, a viewport answers zero instead of panicking.
2558    #[test]
2559    fn a_viewport_reports_zero_intrinsics_while_checking_them() {
2560        let cell = AppCell::new();
2561        let mut app = cell.borrow_mut();
2562        let offset = FixedViewportOffset::zero(&mut app);
2563        let viewport = vertical_viewport(&mut app, offset, vec![]);
2564        crate::object::set_debug_checking_intrinsics(true);
2565        let width = viewport.as_box().get_min_intrinsic_width(&mut app, 0.0);
2566        let height = viewport.as_box().get_max_intrinsic_height(&mut app, 0.0);
2567        crate::object::set_debug_checking_intrinsics(false);
2568        assert_eq!((width, height), (0.0, 0.0));
2569    }
2570
2571    /// A viewport is sized by its parent: its dry layout is the biggest size the constraints
2572    /// allow, and that is the size it takes.
2573    #[test]
2574    fn a_viewport_dry_layout_is_the_biggest_size() {
2575        let cell = AppCell::new();
2576        let mut app = cell.borrow_mut();
2577        let (sliver, _paints) = test_sliver(&mut app, 100.0);
2578        let offset = FixedViewportOffset::zero(&mut app);
2579        let viewport = vertical_viewport(&mut app, offset, vec![sliver]);
2580        let constraints = BoxConstraints::new().max_width(200.0).max_height(400.0);
2581        let dry = viewport.as_box().get_dry_layout(&mut app, constraints);
2582        first_frame(&mut app, viewport.as_box(), constraints);
2583        assert_eq!(dry, Size::new(200.0, 400.0));
2584        assert_eq!(dry, viewport.size(&app));
2585    }
2586
2587    /// `viewport_test.dart`: the slivers are laid out one after the other from the leading edge,
2588    /// and the viewport reports the total scroll extent to its offset.
2589    #[test]
2590    fn a_viewport_lays_its_slivers_out_in_order() {
2591        let cell = AppCell::new();
2592        let mut app = cell.borrow_mut();
2593        let (first, first_paints) = test_sliver(&mut app, 100.0);
2594        let (second, second_paints) = test_sliver(&mut app, 300.0);
2595        let offset = FixedViewportOffset::zero(&mut app);
2596        let viewport = vertical_viewport(&mut app, offset, vec![first, second]);
2597
2598        first_frame(
2599            &mut app,
2600            viewport.as_box(),
2601            BoxConstraints::tight(Size::new(200.0, 400.0)),
2602        );
2603
2604        assert_eq!(viewport.size(&app), Size::new(200.0, 400.0));
2605        assert_eq!(viewport.center(&app), Some(first));
2606        assert_eq!(first.geometry(&app).paint_extent, 100.0);
2607        assert_eq!(second.geometry(&app).paint_extent, 300.0);
2608        assert_eq!(viewport.paint_offset_of(&app, first), Offset::ZERO);
2609        assert_eq!(
2610            viewport.paint_offset_of(&app, second),
2611            Offset::new(0.0, 100.0)
2612        );
2613        assert_eq!(first_paints.take(), vec![Offset::ZERO]);
2614        assert_eq!(second_paints.take(), vec![Offset::new(0.0, 100.0)]);
2615    }
2616
2617    /// A non-zero offset scrolls the content: the first sliver is partly scrolled off and the
2618    /// second moves up by the same amount.
2619    #[test]
2620    fn a_scrolled_viewport_moves_its_slivers() {
2621        let cell = AppCell::new();
2622        let mut app = cell.borrow_mut();
2623        let (first, _) = test_sliver(&mut app, 100.0);
2624        let (second, _) = test_sliver(&mut app, 300.0);
2625        let offset = FixedViewportOffset::new(&mut app, 60.0);
2626        let viewport = vertical_viewport(&mut app, offset, vec![first, second]);
2627
2628        first_frame(
2629            &mut app,
2630            viewport.as_box(),
2631            BoxConstraints::tight(Size::new(200.0, 400.0)),
2632        );
2633
2634        assert_eq!(first.constraints(&app).scroll_offset, 60.0);
2635        assert_eq!(first.geometry(&app).paint_extent, 40.0);
2636        assert_eq!(viewport.paint_offset_of(&app, first), Offset::ZERO);
2637        assert_eq!(
2638            viewport.paint_offset_of(&app, second),
2639            Offset::new(0.0, 40.0)
2640        );
2641        assert_eq!(viewport.scroll_offset_of(&app, second, 0.0), 100.0);
2642    }
2643
2644    /// A hit lands on the sliver that covers the position, in the viewport's coordinate system.
2645    #[test]
2646    fn a_viewport_hit_tests_the_sliver_under_the_position() {
2647        let cell = AppCell::new();
2648        let mut app = cell.borrow_mut();
2649        let (first, _) = test_sliver(&mut app, 100.0);
2650        let (second, _) = test_sliver(&mut app, 300.0);
2651        let offset = FixedViewportOffset::zero(&mut app);
2652        let viewport = vertical_viewport(&mut app, offset, vec![first, second]);
2653        first_frame(
2654            &mut app,
2655            viewport.as_box(),
2656            BoxConstraints::tight(Size::new(200.0, 400.0)),
2657        );
2658
2659        let mut result = HitTestResult::new();
2660        let hit = viewport.hit_test(
2661            &mut app,
2662            &mut BoxHitTestResult::wrap(&mut result),
2663            Offset::new(20.0, 150.0),
2664        );
2665
2666        assert!(hit);
2667        assert_eq!(result.path().len(), 2);
2668        // The viewport and the second sliver, which starts at 100.
2669        assert_eq!(
2670            viewport.compute_child_main_axis_position(&app, second, 150.0),
2671            50.0
2672        );
2673    }
2674
2675    /// A sliver's hit test entry carries the viewport's paint transform, so pointer events
2676    /// reach it in its own coordinates.
2677    #[test]
2678    fn a_sliver_hit_entry_maps_into_the_sliver() {
2679        let cell = AppCell::new();
2680        let mut app = cell.borrow_mut();
2681        let (first, _) = test_sliver(&mut app, 100.0);
2682        let (second, _) = test_sliver(&mut app, 300.0);
2683        let offset = FixedViewportOffset::zero(&mut app);
2684        let viewport = vertical_viewport(&mut app, offset, vec![first, second]);
2685        first_frame(
2686            &mut app,
2687            viewport.as_box(),
2688            BoxConstraints::tight(Size::new(200.0, 400.0)),
2689        );
2690
2691        let mut result = HitTestResult::new();
2692        viewport.hit_test(
2693            &mut app,
2694            &mut BoxHitTestResult::wrap(&mut result),
2695            Offset::new(20.0, 150.0),
2696        );
2697
2698        let transform = result.path()[0].transform().unwrap();
2699        assert_eq!(
2700            inset_painting::transform_point(&transform, Offset::new(20.0, 150.0)),
2701            Offset::new(20.0, 50.0)
2702        );
2703    }
2704
2705    /// The cache extent widens the region slivers are asked to lay out for.
2706    #[test]
2707    fn the_cache_extent_widens_the_laid_out_region() {
2708        let cell = AppCell::new();
2709        let mut app = cell.borrow_mut();
2710        let (sliver, _) = test_sliver(&mut app, 1000.0);
2711        let offset = FixedViewportOffset::zero(&mut app);
2712        let viewport = vertical_viewport(&mut app, offset, vec![sliver]);
2713        viewport.set_scroll_cache_extent(&mut app, Some(ScrollCacheExtent::Pixels(100.0)));
2714
2715        first_frame(
2716            &mut app,
2717            viewport.as_box(),
2718            BoxConstraints::tight(Size::new(200.0, 400.0)),
2719        );
2720
2721        assert_eq!(sliver.constraints(&app).remaining_cache_extent, 500.0);
2722        assert_eq!(sliver.geometry(&app).cache_extent, 500.0);
2723        assert_eq!(sliver.geometry(&app).paint_extent, 400.0);
2724
2725        viewport.set_scroll_cache_extent(&mut app, Some(ScrollCacheExtent::Viewport(0.5)));
2726        viewport.as_box().layout(
2727            &mut app,
2728            BoxConstraints::tight(Size::new(200.0, 400.0)),
2729            false,
2730        );
2731        assert_eq!(sliver.constraints(&app).remaining_cache_extent, 600.0);
2732    }
2733
2734    /// `RenderShrinkWrappingViewport` sizes itself to the total extent of its slivers, up to the
2735    /// incoming constraints.
2736    #[test]
2737    fn a_shrink_wrapping_viewport_sizes_itself_to_its_slivers() {
2738        let cell = AppCell::new();
2739        let mut app = cell.borrow_mut();
2740        let (first, _) = test_sliver(&mut app, 60.0);
2741        let (second, _) = test_sliver(&mut app, 90.0);
2742        let offset = FixedViewportOffset::zero(&mut app);
2743        let viewport = RenderShrinkWrappingViewport::new(
2744            &mut app,
2745            AxisDirection::Right,
2746            offset.as_viewport_offset(),
2747            Some(vec![first, second]),
2748        );
2749
2750        first_frame(
2751            &mut app,
2752            viewport.as_box(),
2753            BoxConstraints::new()
2754                .max_width(200.0)
2755                .min_width(200.0)
2756                .max_height(400.0),
2757        );
2758
2759        assert_eq!(viewport.size(&app), Size::new(200.0, 150.0));
2760        assert_eq!(
2761            viewport.paint_offset_of(&app, second),
2762            Offset::new(0.0, 60.0)
2763        );
2764        assert_eq!(viewport.index_of_first_child(&app), 0);
2765        assert_eq!(viewport.label_for_child(1), "child 1");
2766    }
2767
2768    /// A box adapter inside a viewport reaches the box protocol: the box is laid out with the
2769    /// viewport's cross axis extent and painted at the sliver's paint offset.
2770    #[test]
2771    fn a_viewport_hosts_a_box_through_the_adapter() {
2772        let cell = AppCell::new();
2773        let mut app = cell.borrow_mut();
2774        let child =
2775            RenderConstrainedBox::new(&mut app, BoxConstraints::tight_for(None, Some(80.0)), None);
2776        let adapter = RenderSliverToBoxAdapter::new(&mut app, Some(child.as_box()));
2777        let offset = FixedViewportOffset::zero(&mut app);
2778        let viewport = vertical_viewport(&mut app, offset, vec![adapter.as_sliver()]);
2779
2780        first_frame(
2781            &mut app,
2782            viewport.as_box(),
2783            BoxConstraints::tight(Size::new(200.0, 400.0)),
2784        );
2785
2786        assert_eq!(child.size(&app), Size::new(200.0, 80.0));
2787        assert_eq!(adapter.geometry(&app).scroll_extent, 80.0);
2788        assert_eq!(
2789            child.as_box().local_to_global(&app, Offset::ZERO, None),
2790            Offset::ZERO
2791        );
2792    }
2793
2794    /// The viewport is the `RenderAbstractViewport` that `maybe_of` finds from a descendant.
2795    #[test]
2796    fn maybe_of_finds_the_enclosing_viewport() {
2797        let cell = AppCell::new();
2798        let mut app = cell.borrow_mut();
2799        let child =
2800            RenderConstrainedBox::new(&mut app, BoxConstraints::tight_for(None, Some(80.0)), None);
2801        let adapter = RenderSliverToBoxAdapter::new(&mut app, Some(child.as_box()));
2802        let offset = FixedViewportOffset::zero(&mut app);
2803        let viewport = vertical_viewport(&mut app, offset, vec![adapter.as_sliver()]);
2804        first_frame(
2805            &mut app,
2806            viewport.as_box(),
2807            BoxConstraints::tight(Size::new(200.0, 400.0)),
2808        );
2809
2810        let found = AnyRenderAbstractViewport::maybe_of(&app, Some(child.as_object()));
2811        assert_eq!(found, Some(viewport.as_abstract_viewport()));
2812        assert!(AnyRenderAbstractViewport::maybe_of(&app, None).is_none());
2813    }
2814
2815    /// `get_offset_to_reveal` reports the offset that brings a descendant box to the leading edge.
2816    #[test]
2817    fn get_offset_to_reveal_reports_the_leading_edge_offset() {
2818        let cell = AppCell::new();
2819        let mut app = cell.borrow_mut();
2820        let leading =
2821            RenderConstrainedBox::new(&mut app, BoxConstraints::tight_for(None, Some(100.0)), None);
2822        let first = RenderSliverToBoxAdapter::new(&mut app, Some(leading.as_box()));
2823        let target =
2824            RenderConstrainedBox::new(&mut app, BoxConstraints::tight_for(None, Some(50.0)), None);
2825        let second = RenderSliverToBoxAdapter::new(&mut app, Some(target.as_box()));
2826        let offset = FixedViewportOffset::zero(&mut app);
2827        let viewport = vertical_viewport(
2828            &mut app,
2829            offset,
2830            vec![first.as_sliver(), second.as_sliver()],
2831        );
2832        first_frame(
2833            &mut app,
2834            viewport.as_box(),
2835            BoxConstraints::tight(Size::new(200.0, 400.0)),
2836        );
2837
2838        let revealed = RenderAbstractViewport::get_offset_to_reveal(
2839            viewport,
2840            &app,
2841            target.as_object(),
2842            0.0,
2843            None,
2844            None,
2845        );
2846        assert_eq!(revealed.offset, 100.0);
2847        // At that offset the target sits at the leading edge of the viewport.
2848        assert_eq!(revealed.rect.top, 0.0);
2849        assert_eq!(revealed.rect.bottom, 50.0);
2850    }
2851
2852    /// `RevealedOffset::clamp_offset` picks the nearer edge, and `None` when already visible.
2853    #[test]
2854    fn clamp_offset_picks_the_nearer_edge() {
2855        let leading = RevealedOffset::new(100.0, Rect::ZERO);
2856        let trailing = RevealedOffset::new(20.0, Rect::ZERO);
2857        assert_eq!(
2858            RevealedOffset::clamp_offset(leading, trailing, 200.0).map(|offset| offset.offset),
2859            Some(100.0)
2860        );
2861        assert_eq!(
2862            RevealedOffset::clamp_offset(leading, trailing, 0.0).map(|offset| offset.offset),
2863            Some(20.0)
2864        );
2865        assert!(RevealedOffset::clamp_offset(leading, trailing, 50.0).is_none());
2866    }
2867
2868    /// The paint order decides which sliver paints last and which is hit-tested first.
2869    #[test]
2870    fn the_paint_order_reverses_the_hit_test_order() {
2871        let cell = AppCell::new();
2872        let mut app = cell.borrow_mut();
2873        let (first, _) = test_sliver(&mut app, 100.0);
2874        let (second, _) = test_sliver(&mut app, 300.0);
2875        let offset = FixedViewportOffset::zero(&mut app);
2876        let viewport = vertical_viewport(&mut app, offset, vec![first, second]);
2877        first_frame(
2878            &mut app,
2879            viewport.as_box(),
2880            BoxConstraints::tight(Size::new(200.0, 400.0)),
2881        );
2882
2883        assert_eq!(viewport.children_in_paint_order(&app), vec![second, first]);
2884        assert_eq!(
2885            viewport.children_in_hit_test_order(&app),
2886            vec![first, second]
2887        );
2888
2889        viewport.set_paint_order(&mut app, SliverPaintOrder::LastIsTop);
2890        assert_eq!(viewport.children_in_paint_order(&app), vec![first, second]);
2891        assert_eq!(
2892            viewport.children_in_hit_test_order(&app),
2893            vec![second, first]
2894        );
2895    }
2896}