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            RenderObject::apply_paint_transform(self, app, child.as_object(), &mut transform);
922            let child_main_axis_position =
923                self.compute_child_main_axis_position(app, child, main_axis_position);
924            let is_hit =
925                result.add_with_out_of_band_position(None, Some(transform), None, |result| {
926                    child.hit_test(
927                        app,
928                        &mut SliverHitTestResult::wrap(result),
929                        child_main_axis_position,
930                        cross_axis_position,
931                    )
932                });
933            if is_hit {
934                return true;
935            }
936        }
937        false
938    }
939
940    /// The body of Flutter's `getOffsetToReveal` override.
941    fn get_offset_to_reveal(
942        self: RenderHandle<Self>,
943        app: &App,
944        target: AnyRenderObject,
945        alignment: f64,
946        rect: Option<Rect>,
947        _axis: Option<Axis>,
948    ) -> RevealedOffset {
949        // One dimensional viewport has only one axis, override if it was provided/may be
950        // mismatched.
951        let axis = self.axis(app);
952        let this = self.as_object();
953
954        // Steps to convert `rect` (from a RenderBox coordinate system) to its scroll offset
955        // within this viewport (not in the exact order):
956        //
957        // 1. Pick the outermost RenderBox (between which, and the viewport, there is nothing but
958        // RenderSlivers) as an intermediate reference frame (the `pivot`), convert `rect` to
959        // that coordinate space.
960        //
961        // 2. Convert `rect` from the `pivot` coordinate space to its sliver parent's sliver
962        // coordinate system (i.e., to a scroll offset), based on the axis direction and growth
963        // direction of the parent.
964        //
965        // 3. Convert the scroll offset to its sliver parent's coordinate space using
966        // `child_scroll_offset`, until we reach the viewport.
967        //
968        // 4. Make the final conversion from the outmost sliver to the viewport using
969        // `scroll_offset_of`.
970
971        let mut leading_scroll_offset = 0.0;
972        // Starting at `target` and walking towards the root:
973        //  - `child` will be the last object before we reach this viewport, and
974        //  - `pivot` will be the last RenderBox before we reach this viewport.
975        let mut child = target;
976        let mut pivot = None;
977        // ... between viewport and `target` (`target` included).
978        let mut only_slivers = target.as_sliver().is_some();
979        let mut rect = rect;
980        while child.parent(app) != Some(this) {
981            let parent = child
982                .parent(app)
983                .expect("target is a descendant of this viewport");
984            if let Some(box_child) = child.as_box() {
985                pivot = Some(box_child);
986            }
987            match parent.as_sliver() {
988                Some(sliver) => {
989                    leading_scroll_offset += sliver
990                        .child_scroll_offset(app, child)
991                        .expect("a sliver parent reports its child's scroll offset");
992                }
993                None => {
994                    only_slivers = false;
995                    leading_scroll_offset = 0.0;
996                }
997            }
998            child = parent;
999        }
1000
1001        // `rect` in the new intermediate coordinate system.
1002        let rect_local;
1003        // Our new reference frame render object's main axis extent.
1004        let pivot_extent;
1005        let growth_direction;
1006
1007        // `leading_scroll_offset` is currently the scrollOffset of our new reference frame
1008        // (`pivot` or `target`), within `child`.
1009        if let Some(pivot) = pivot {
1010            debug_assert!(pivot.as_object().parent(app).is_some());
1011            debug_assert_ne!(pivot.as_object().parent(app), Some(this));
1012            debug_assert_ne!(pivot.as_object(), this);
1013            let pivot_parent = pivot
1014                .as_object()
1015                .parent(app)
1016                .and_then(AnyRenderObject::as_sliver)
1017                .expect("only sliver parents are supported between a pivot and the viewport");
1018            growth_direction = pivot_parent.constraints(app).growth_direction;
1019            pivot_extent = match axis {
1020                Axis::Horizontal => pivot.size(app).width(),
1021                Axis::Vertical => pivot.size(app).height(),
1022            };
1023            let rect = *rect.get_or_insert_with(|| target.paint_bounds(app));
1024            rect_local =
1025                transform_rect(&target.get_transform_to(app, Some(pivot.as_object())), rect);
1026        } else if only_slivers {
1027            // `pivot` does not exist. We'll have to make up one from `target`, the innermost
1028            // sliver.
1029            let target_sliver = target.as_sliver().expect("only_slivers");
1030            growth_direction = target_sliver.constraints(app).growth_direction;
1031            pivot_extent = target_sliver.geometry(app).scroll_extent;
1032            rect_local = *rect.get_or_insert_with(|| {
1033                let constraints = target_sliver.constraints(app);
1034                let geometry = target_sliver.geometry(app);
1035                match axis {
1036                    Axis::Horizontal => Rect::from_ltwh(
1037                        0.0,
1038                        0.0,
1039                        geometry.scroll_extent,
1040                        constraints.cross_axis_extent,
1041                    ),
1042                    Axis::Vertical => Rect::from_ltwh(
1043                        0.0,
1044                        0.0,
1045                        constraints.cross_axis_extent,
1046                        geometry.scroll_extent,
1047                    ),
1048                }
1049            });
1050        } else {
1051            let rect = rect.expect("a non-sliver target must be given a rect");
1052            return RevealedOffset::new(self.offset(app).pixels(app), rect);
1053        }
1054
1055        debug_assert_eq!(child.parent(app), Some(this));
1056        let sliver = child
1057            .as_sliver()
1058            .expect("a viewport's children are slivers");
1059
1060        // The scroll offset of `rect` within `child`.
1061        leading_scroll_offset += match apply_growth_direction_to_axis_direction(
1062            self.axis_direction(app),
1063            growth_direction,
1064        ) {
1065            AxisDirection::Up => pivot_extent - rect_local.bottom,
1066            AxisDirection::Left => pivot_extent - rect_local.right,
1067            AxisDirection::Right => rect_local.left,
1068            AxisDirection::Down => rect_local.top,
1069        };
1070
1071        // So far leading_scroll_offset is the scroll offset of `rect` in the `child` sliver's
1072        // sliver coordinate system. The sign of this value indicates whether the `rect`
1073        // protrudes the leading edge of the `child` sliver. When this value is non-negative and
1074        // `child`'s `max_scroll_obstruction_extent` is greater than 0, we assume `rect` can't be
1075        // obstructed by the leading edge of the viewport (i.e. it is pinned to the leading edge).
1076        let is_pinned = sliver.geometry(app).max_scroll_obstruction_extent > 0.0
1077            && leading_scroll_offset >= 0.0;
1078
1079        // The scroll offset in the viewport to `rect`.
1080        leading_scroll_offset = self.scroll_offset_of(app, sliver, leading_scroll_offset);
1081
1082        // This step assumes the viewport's layout is up-to-date, i.e., if offset.pixels is
1083        // changed after the last perform_layout, the new scroll position will not be accounted
1084        // for.
1085        let transform = target.get_transform_to(app, Some(this));
1086        let mut target_rect = transform_rect(&transform, rect.expect("set above"));
1087        let extent_of_pinned_slivers = self.max_scroll_obstruction_extent_before(app, sliver);
1088
1089        match sliver.constraints(app).growth_direction {
1090            GrowthDirection::Forward => {
1091                if is_pinned && alignment <= 0.0 {
1092                    return RevealedOffset::new(f64::INFINITY, target_rect);
1093                }
1094                leading_scroll_offset -= extent_of_pinned_slivers;
1095            }
1096            GrowthDirection::Reverse => {
1097                if is_pinned && alignment >= 1.0 {
1098                    return RevealedOffset::new(f64::NEG_INFINITY, target_rect);
1099                }
1100                // If child's growth direction is reverse, when viewport.offset is
1101                // `leading_scroll_offset`, it is positioned just outside of the leading edge of
1102                // the viewport.
1103                leading_scroll_offset -= match axis {
1104                    Axis::Vertical => target_rect.height(),
1105                    Axis::Horizontal => target_rect.width(),
1106                };
1107            }
1108        }
1109
1110        let size = self.size(app);
1111        let main_axis_extent_difference = match axis {
1112            Axis::Horizontal => size.width() - extent_of_pinned_slivers - rect_local.width(),
1113            Axis::Vertical => size.height() - extent_of_pinned_slivers - rect_local.height(),
1114        };
1115
1116        let target_offset = leading_scroll_offset - main_axis_extent_difference * alignment;
1117        let offset_difference = self.offset(app).pixels(app) - target_offset;
1118
1119        target_rect = match self.axis_direction(app) {
1120            AxisDirection::Up => target_rect.translate(0.0, -offset_difference),
1121            AxisDirection::Down => target_rect.translate(0.0, offset_difference),
1122            AxisDirection::Left => target_rect.translate(-offset_difference, 0.0),
1123            AxisDirection::Right => target_rect.translate(offset_difference, 0.0),
1124        };
1125
1126        RevealedOffset::new(target_offset, target_rect)
1127    }
1128
1129    /// The offset at which the given `child` should be painted.
1130    ///
1131    /// The returned offset is from the top left corner of the inside of the viewport to the top
1132    /// left corner of the paint coordinate system of the `child`.
1133    ///
1134    /// See also:
1135    ///
1136    ///  * [`paint_offset_of`](Self::paint_offset_of), which uses the layout offset and growth
1137    ///    direction computed for the child during layout.
1138    fn compute_absolute_paint_offset(
1139        self: RenderHandle<Self>,
1140        app: &App,
1141        child: AnyRenderSliver,
1142        layout_offset: f64,
1143        growth_direction: GrowthDirection,
1144    ) -> Offset {
1145        // this is only usable once we have a size
1146        debug_assert!(self.has_size(app));
1147        let paint_extent = child.geometry(app).paint_extent;
1148        let size = self.size(app);
1149        match apply_growth_direction_to_axis_direction(self.axis_direction(app), growth_direction) {
1150            AxisDirection::Up => Offset::new(0.0, size.height() - layout_offset - paint_extent),
1151            AxisDirection::Left => Offset::new(size.width() - layout_offset - paint_extent, 0.0),
1152            AxisDirection::Right => Offset::new(layout_offset, 0.0),
1153            AxisDirection::Down => Offset::new(0.0, layout_offset),
1154        }
1155    }
1156
1157    // API TO BE IMPLEMENTED BY SUBCLASSES
1158
1159    /// Whether the contents of this viewport would paint outside the bounds of the viewport if
1160    /// [`paint`](Self::paint) did not clip.
1161    ///
1162    /// This property enables an optimization whereby [`paint`](Self::paint) can skip applying a
1163    /// clip if the contents of the viewport are known to paint entirely within its bounds.
1164    fn has_visual_overflow(self: RenderHandle<Self>, app: &App) -> bool;
1165
1166    /// Called during [`layout_child_sequence`](Self::layout_child_sequence) for each child.
1167    ///
1168    /// Typically used by implementors to update any out-of-band data, such as the max scroll
1169    /// extent, for each child.
1170    fn update_out_of_band_data(
1171        self: RenderHandle<Self>,
1172        app: &mut App,
1173        growth_direction: GrowthDirection,
1174        child_layout_geometry: SliverGeometry,
1175    );
1176
1177    /// Called during [`layout_child_sequence`](Self::layout_child_sequence) to store the layout
1178    /// offset for the given child.
1179    ///
1180    /// Different implementors use different representations for their children's layout offset
1181    /// (e.g., logical or physical coordinates). This function lets them transform the child's
1182    /// layout offset before storing it in the child's parent data.
1183    fn update_child_layout_offset(
1184        self: RenderHandle<Self>,
1185        app: &mut App,
1186        child: AnyRenderSliver,
1187        layout_offset: f64,
1188        growth_direction: GrowthDirection,
1189    );
1190
1191    /// The offset at which the given `child` should be painted.
1192    ///
1193    /// The returned offset is from the top left corner of the inside of the viewport to the top
1194    /// left corner of the paint coordinate system of the `child`.
1195    ///
1196    /// See also:
1197    ///
1198    ///  * [`compute_absolute_paint_offset`](Self::compute_absolute_paint_offset), which computes
1199    ///    the paint offset from an explicit layout offset and growth direction instead of using
1200    ///    the values computed for the child during layout.
1201    fn paint_offset_of(self: RenderHandle<Self>, app: &App, child: AnyRenderSliver) -> Offset;
1202
1203    /// Returns the scroll offset within the viewport for the given `scroll_offset_within_child`
1204    /// within the given `child`.
1205    ///
1206    /// The returned value is an estimate that assumes the slivers within the viewport do not
1207    /// change the layout extent in response to changes in their scroll offset.
1208    fn scroll_offset_of(
1209        self: RenderHandle<Self>,
1210        app: &App,
1211        child: AnyRenderSliver,
1212        scroll_offset_within_child: f64,
1213    ) -> f64;
1214
1215    /// Returns the total scroll obstruction extent of all slivers in the viewport before
1216    /// `child`.
1217    ///
1218    /// This is the extent by which the actual area in which content can scroll is reduced. For
1219    /// example, an app bar that is pinned at the top will reduce the area in which content can
1220    /// actually scroll by the height of the app bar.
1221    fn max_scroll_obstruction_extent_before(
1222        self: RenderHandle<Self>,
1223        app: &App,
1224        child: AnyRenderSliver,
1225    ) -> f64;
1226
1227    /// Converts the `parent_main_axis_position` into the child's coordinate system.
1228    ///
1229    /// The `parent_main_axis_position` is a distance from the top edge (for vertical viewports)
1230    /// or left edge (for horizontal viewports) of the viewport bounds. This describes a line,
1231    /// perpendicular to the viewport's main axis, heretofore known as the target line.
1232    ///
1233    /// The child's coordinate system's origin in the main axis is at the leading edge of the
1234    /// given child, as given by the child's [`SliverConstraints::axis_direction`] and
1235    /// [`SliverConstraints::growth_direction`].
1236    ///
1237    /// This method returns the distance from the leading edge of the given child to the target
1238    /// line described above.
1239    fn compute_child_main_axis_position(
1240        self: RenderHandle<Self>,
1241        app: &App,
1242        child: AnyRenderSliver,
1243        parent_main_axis_position: f64,
1244    ) -> f64;
1245
1246    /// The index of the first child of the viewport relative to the center child.
1247    ///
1248    /// For example, the center child has index zero and the first child in the reverse growth
1249    /// direction has index -1.
1250    fn index_of_first_child(self: RenderHandle<Self>, app: &App) -> i32;
1251
1252    /// A short string to identify the child with the given index.
1253    fn label_for_child(self: RenderHandle<Self>, index: i32) -> String;
1254
1255    /// Walks the children of the viewport, in the order that they should be painted.
1256    ///
1257    /// This is the reverse order of
1258    /// [`children_in_hit_test_order`](Self::children_in_hit_test_order).
1259    fn children_in_paint_order(self: RenderHandle<Self>, app: &App) -> Vec<AnyRenderSliver> {
1260        match self.paint_order(app) {
1261            SliverPaintOrder::FirstIsTop => self.children_last_to_first(app),
1262            SliverPaintOrder::LastIsTop => self.children_as_list(app),
1263        }
1264    }
1265
1266    /// Walks the children of the viewport, in the order that hit-testing should use.
1267    ///
1268    /// This is the reverse order of [`children_in_paint_order`](Self::children_in_paint_order).
1269    fn children_in_hit_test_order(self: RenderHandle<Self>, app: &App) -> Vec<AnyRenderSliver> {
1270        match self.paint_order(app) {
1271            SliverPaintOrder::FirstIsTop => self.children_as_list(app),
1272            SliverPaintOrder::LastIsTop => self.children_last_to_first(app),
1273        }
1274    }
1275
1276    /// Flutter's `_childrenLastToFirst`.
1277    fn children_last_to_first(self: RenderHandle<Self>, app: &App) -> Vec<AnyRenderSliver> {
1278        let mut children = Vec::new();
1279        let mut child = self.last_child(app);
1280        while let Some(current) = child {
1281            children.push(current);
1282            child = self.child_before(app, current);
1283        }
1284        children
1285    }
1286
1287    /// The body of Flutter's `showOnScreen` override.
1288    fn show_on_screen(
1289        self: RenderHandle<Self>,
1290        app: &mut App,
1291        descendant: Option<AnyRenderObject>,
1292        rect: Option<Rect>,
1293        duration: Duration,
1294        curve: Rc<dyn Curve>,
1295    ) {
1296        let offset = self.offset(app);
1297        if !offset.allow_implicit_scrolling(app) {
1298            return crate::object::RenderObjectBase::show_on_screen(
1299                self, app, descendant, rect, duration, curve,
1300            );
1301        }
1302
1303        let new_rect = show_in_viewport(
1304            app,
1305            descendant,
1306            rect,
1307            self.as_abstract_viewport(),
1308            offset,
1309            duration,
1310            Rc::clone(&curve),
1311        );
1312        crate::object::RenderObjectBase::show_on_screen(self, app, None, new_rect, duration, curve);
1313    }
1314}
1315
1316/// Dart's `_offset.addListener(markNeedsLayout)` tear-off body: a named function so that the
1317/// listener added on attach compares equal to the one removed on detach.
1318fn viewport_offset_changed<T: RenderViewportBase>(
1319    this: inset_foundation::Handle<T>,
1320    app: &mut App,
1321) {
1322    RenderHandle::from_handle(this).mark_needs_layout(app);
1323}
1324
1325/// Make (a portion of) the given `descendant` of the given `viewport` fully visible in the
1326/// `viewport` by manipulating the provided [`AnyViewportOffset`] `offset`.
1327///
1328/// The optional `rect` parameter describes which area of the `descendant` should be shown in the
1329/// viewport. If `rect` is `None`, the entire `descendant` will be revealed. The `rect` parameter
1330/// is interpreted relative to the coordinate system of `descendant`.
1331///
1332/// The returned [`Rect`] describes the new location of `descendant` or `rect` in the viewport
1333/// after it has been revealed. See [`RevealedOffset::rect`] for a full definition of this rect.
1334///
1335/// If `descendant` is `None`, this is a no-op and `rect` is returned.
1336///
1337/// The `duration` parameter can be set to a non-zero value to animate the target object into the
1338/// viewport with an animation defined by `curve`.
1339///
1340/// Flutter's `RenderViewportBase.showInViewport` static.
1341pub fn show_in_viewport(
1342    app: &mut App,
1343    descendant: Option<AnyRenderObject>,
1344    rect: Option<Rect>,
1345    viewport: AnyRenderAbstractViewport,
1346    offset: AnyViewportOffset,
1347    duration: Duration,
1348    curve: Rc<dyn Curve>,
1349) -> Option<Rect> {
1350    let Some(descendant) = descendant else {
1351        return rect;
1352    };
1353    let leading_edge_offset = viewport.get_offset_to_reveal(app, descendant, 0.0, rect, None);
1354    let trailing_edge_offset = viewport.get_offset_to_reveal(app, descendant, 1.0, rect, None);
1355    let current_offset = offset.pixels(app);
1356    let target_offset =
1357        RevealedOffset::clamp_offset(leading_edge_offset, trailing_edge_offset, current_offset);
1358    let Some(target_offset) = target_offset else {
1359        // `descendant` is between leading and trailing edge and hence already fully shown on
1360        // screen. No action necessary.
1361        let viewport_object = viewport.as_object(app);
1362        debug_assert!(viewport_object.parent(app).is_some());
1363        let transform = descendant.get_transform_to(app, viewport_object.parent(app));
1364        return Some(transform_rect(
1365            &transform,
1366            rect.unwrap_or_else(|| descendant.paint_bounds(app)),
1367        ));
1368    };
1369
1370    offset.move_to(app, target_offset.offset, Some(duration), Some(curve), None);
1371    Some(target_offset.rect)
1372}
1373
1374/// A render object that is bigger on the inside.
1375///
1376/// [`RenderViewport`] is the visual workhorse of the scrolling machinery. It displays a subset of
1377/// its children according to its own dimensions and the given
1378/// [`offset`](RenderViewportBase::offset). As the offset varies, different children are visible
1379/// through the viewport.
1380///
1381/// [`RenderViewport`] hosts a bidirectional list of slivers in a single shared [`Axis`], anchored
1382/// on a [`center`](Self::center) sliver, which is placed at the zero scroll offset. The center
1383/// widget is displayed in the viewport according to the [`anchor`](Self::anchor) property.
1384///
1385/// Slivers that are earlier in the child list than [`center`](Self::center) are displayed in
1386/// reverse order in the reverse [`RenderViewportBase::axis_direction`] starting from the center.
1387///
1388/// [`RenderViewport`] cannot contain [`RenderBox`] children directly. Instead, use a
1389/// [`crate::RenderSliverList`], [`crate::RenderSliverFixedExtentList`], or a
1390/// [`crate::RenderSliverToBoxAdapter`], for example.
1391pub struct RenderViewport {
1392    render_object: RenderObjectData,
1393    render_box: RenderBoxData,
1394    container: ContainerRenderObjectData<AnyRenderSliver>,
1395    viewport: RenderViewportBaseData,
1396    anchor: f64,
1397    center: Option<AnyRenderSliver>,
1398    // Out-of-band data computed during layout.
1399    min_scroll_extent: f64,
1400    max_scroll_extent: f64,
1401    has_visual_overflow: bool,
1402}
1403
1404impl RenderViewport {
1405    /// Dart's `_maxLayoutCyclesPerChild`.
1406    const MAX_LAYOUT_CYCLES_PER_CHILD: usize = 10;
1407
1408    /// Creates a viewport for [`AnyRenderSliver`] objects.
1409    ///
1410    /// If the `center` is not specified, then the first child in the `children` list, if any, is
1411    /// used.
1412    ///
1413    /// For testing purposes, consider passing a [`crate::FixedViewportOffset`] as the `offset`.
1414    pub fn new(
1415        app: &mut App,
1416        cross_axis_direction: AxisDirection,
1417        offset: AnyViewportOffset,
1418        children: Option<Vec<AnyRenderSliver>>,
1419        center: Option<AnyRenderSliver>,
1420    ) -> RenderHandle<RenderViewport> {
1421        let this = RenderHandle::new_box(
1422            app,
1423            RenderViewport {
1424                render_object: RenderObjectData::new(),
1425                render_box: RenderBoxData::new(),
1426                container: ContainerRenderObjectData::new(),
1427                viewport: RenderViewportBaseData::new(cross_axis_direction, offset),
1428                anchor: 0.0,
1429                center,
1430                min_scroll_extent: 0.0,
1431                max_scroll_extent: 0.0,
1432                has_visual_overflow: false,
1433            },
1434        );
1435        this.add_all(app, children);
1436        if center.is_none()
1437            && let Some(first_child) = this.first_child(app)
1438        {
1439            this.get_mut(app).center = Some(first_child);
1440        }
1441        this
1442    }
1443
1444    /// The relative position of the zero scroll offset.
1445    ///
1446    /// For example, if the [`RenderViewportBase::axis_direction`] is [`AxisDirection::Down`] or
1447    /// [`AxisDirection::Up`] and the anchor is 0.5, then the zero scroll offset is vertically
1448    /// centered within the viewport. If the anchor is 1.0, and the axis direction is
1449    /// [`AxisDirection::Right`], then the zero scroll offset is on the left edge of the viewport.
1450    pub fn anchor(self: RenderHandle<Self>, app: &App) -> f64 {
1451        self.get(app).anchor
1452    }
1453
1454    /// Sets [`anchor`](Self::anchor).
1455    pub fn set_anchor(self: RenderHandle<Self>, app: &mut App, value: f64) {
1456        debug_assert!((0.0..=1.0).contains(&value));
1457        if value == self.get(app).anchor {
1458            return;
1459        }
1460        self.get_mut(app).anchor = value;
1461        self.mark_needs_layout(app);
1462    }
1463
1464    /// The first child in the [`GrowthDirection::Forward`] growth direction.
1465    ///
1466    /// This child will be at the position defined by [`anchor`](Self::anchor) when the
1467    /// [`crate::ViewportOffset::pixels`] of [`RenderViewportBase::offset`] is `0`.
1468    ///
1469    /// Children after the center will be placed in the [`RenderViewportBase::axis_direction`]
1470    /// relative to the center. Children before it will be placed in the opposite direction, and
1471    /// will have a growth direction of [`GrowthDirection::Reverse`].
1472    ///
1473    /// The center must be a direct child of the viewport.
1474    pub fn center(self: RenderHandle<Self>, app: &App) -> Option<AnyRenderSliver> {
1475        self.get(app).center
1476    }
1477
1478    /// Sets [`center`](Self::center).
1479    pub fn set_center(self: RenderHandle<Self>, app: &mut App, value: Option<AnyRenderSliver>) {
1480        if value == self.get(app).center {
1481            return;
1482        }
1483        self.get_mut(app).center = value;
1484        self.mark_needs_layout(app);
1485    }
1486
1487    /// Flutter's `_attemptLayout`.
1488    fn attempt_layout(
1489        self: RenderHandle<Self>,
1490        app: &mut App,
1491        main_axis_extent: f64,
1492        cross_axis_extent: f64,
1493        corrected_offset: f64,
1494    ) -> f64 {
1495        debug_assert!(!main_axis_extent.is_nan());
1496        debug_assert!(main_axis_extent >= 0.0);
1497        debug_assert!(main_axis_extent.is_finite());
1498        debug_assert!(cross_axis_extent.is_finite());
1499        debug_assert!(cross_axis_extent >= 0.0);
1500        debug_assert!(corrected_offset.is_finite());
1501        let this = self.get_mut(app);
1502        this.min_scroll_extent = 0.0;
1503        this.max_scroll_extent = 0.0;
1504        this.has_visual_overflow = false;
1505
1506        // center_offset is the offset from the leading edge of the viewport to the zero scroll
1507        // offset (the line between the forward slivers and the reverse slivers).
1508        let anchor = self.get(app).anchor;
1509        let center_offset = main_axis_extent * anchor - corrected_offset;
1510        let reverse_direction_remaining_paint_extent =
1511            clamp_double(center_offset, 0.0, main_axis_extent);
1512        let forward_direction_remaining_paint_extent =
1513            clamp_double(main_axis_extent - center_offset, 0.0, main_axis_extent);
1514
1515        let calculated_cache_extent = self
1516            .scroll_cache_extent(app)
1517            .calculate_cache_offset(main_axis_extent);
1518        self.viewport_data_mut(app).calculated_cache_extent = Some(calculated_cache_extent);
1519
1520        let full_cache_extent = main_axis_extent + 2.0 * calculated_cache_extent;
1521        let center_cache_offset = center_offset + calculated_cache_extent;
1522        let reverse_direction_remaining_cache_extent =
1523            clamp_double(center_cache_offset, 0.0, full_cache_extent);
1524        let forward_direction_remaining_cache_extent = clamp_double(
1525            full_cache_extent - center_cache_offset,
1526            0.0,
1527            full_cache_extent,
1528        );
1529
1530        let center = self.center(app).expect("checked by perform_layout");
1531        let leading_negative_child = self.child_before(app, center);
1532
1533        if let Some(leading_negative_child) = leading_negative_child {
1534            // negative scroll offsets
1535            let result = self.layout_child_sequence(
1536                app,
1537                Some(leading_negative_child),
1538                main_axis_extent.max(center_offset) - main_axis_extent,
1539                0.0,
1540                forward_direction_remaining_paint_extent,
1541                reverse_direction_remaining_paint_extent,
1542                main_axis_extent,
1543                cross_axis_extent,
1544                GrowthDirection::Reverse,
1545                &|this, app, child| this.child_before(app, child),
1546                reverse_direction_remaining_cache_extent,
1547                clamp_double(
1548                    main_axis_extent - center_offset,
1549                    -calculated_cache_extent,
1550                    0.0,
1551                ),
1552            );
1553            if result != 0.0 {
1554                return -result;
1555            }
1556        }
1557
1558        // positive scroll offsets
1559        self.layout_child_sequence(
1560            app,
1561            Some(center),
1562            (-center_offset).max(0.0),
1563            if leading_negative_child.is_none() {
1564                (-center_offset).min(0.0)
1565            } else {
1566                0.0
1567            },
1568            if center_offset >= main_axis_extent {
1569                center_offset
1570            } else {
1571                reverse_direction_remaining_paint_extent
1572            },
1573            forward_direction_remaining_paint_extent,
1574            main_axis_extent,
1575            cross_axis_extent,
1576            GrowthDirection::Forward,
1577            &|this, app, child| this.child_after(app, child),
1578            forward_direction_remaining_cache_extent,
1579            clamp_double(center_offset, -calculated_cache_extent, 0.0),
1580        )
1581    }
1582}
1583
1584impl ContainerRenderObjectMixin for RenderViewport {
1585    type ChildType = AnyRenderSliver;
1586    type ParentDataType = SliverPhysicalContainerParentData;
1587
1588    fn container_data(
1589        self: RenderHandle<Self>,
1590        app: &App,
1591    ) -> &ContainerRenderObjectData<AnyRenderSliver> {
1592        &self.get(app).container
1593    }
1594
1595    fn container_data_mut(
1596        self: RenderHandle<Self>,
1597        app: &mut App,
1598    ) -> &mut ContainerRenderObjectData<AnyRenderSliver> {
1599        &mut self.get_mut(app).container
1600    }
1601}
1602
1603impl RenderViewportBase for RenderViewport {
1604    crate::render_viewport_base_accessors!();
1605
1606    fn has_visual_overflow(self: RenderHandle<Self>, app: &App) -> bool {
1607        self.get(app).has_visual_overflow
1608    }
1609
1610    fn update_out_of_band_data(
1611        self: RenderHandle<Self>,
1612        app: &mut App,
1613        growth_direction: GrowthDirection,
1614        child_layout_geometry: SliverGeometry,
1615    ) {
1616        let this = self.get_mut(app);
1617        match growth_direction {
1618            GrowthDirection::Forward => {
1619                this.max_scroll_extent += child_layout_geometry.scroll_extent
1620            }
1621            GrowthDirection::Reverse => {
1622                this.min_scroll_extent -= child_layout_geometry.scroll_extent
1623            }
1624        }
1625        if child_layout_geometry.has_visual_overflow {
1626            this.has_visual_overflow = true;
1627        }
1628    }
1629
1630    fn update_child_layout_offset(
1631        self: RenderHandle<Self>,
1632        app: &mut App,
1633        child: AnyRenderSliver,
1634        layout_offset: f64,
1635        growth_direction: GrowthDirection,
1636    ) {
1637        let paint_offset =
1638            self.compute_absolute_paint_offset(app, child, layout_offset, growth_direction);
1639        child
1640            .as_object()
1641            .parent_data_of_mut::<SliverPhysicalContainerParentData>(app)
1642            .set_paint_offset(paint_offset);
1643    }
1644
1645    fn paint_offset_of(self: RenderHandle<Self>, app: &App, child: AnyRenderSliver) -> Offset {
1646        child
1647            .as_object()
1648            .parent_data_of::<SliverPhysicalContainerParentData>(app)
1649            .paint_offset()
1650    }
1651
1652    fn scroll_offset_of(
1653        self: RenderHandle<Self>,
1654        app: &App,
1655        child: AnyRenderSliver,
1656        scroll_offset_within_child: f64,
1657    ) -> f64 {
1658        debug_assert_eq!(child.as_object().parent(app), Some(self.as_object()));
1659        match child.constraints(app).growth_direction {
1660            GrowthDirection::Forward => {
1661                let mut scroll_offset_to_child = 0.0;
1662                let mut current = self.center(app);
1663                while current != Some(child) {
1664                    let sliver = current.expect("the center precedes a forward child");
1665                    scroll_offset_to_child += sliver.geometry(app).scroll_extent;
1666                    current = self.child_after(app, sliver);
1667                }
1668                scroll_offset_to_child + scroll_offset_within_child
1669            }
1670            GrowthDirection::Reverse => {
1671                let mut scroll_offset_to_child = 0.0;
1672                let center = self.center(app).expect("a laid-out viewport has a center");
1673                let mut current = self.child_before(app, center);
1674                while current != Some(child) {
1675                    let sliver = current.expect("a reverse child precedes the center");
1676                    scroll_offset_to_child -= sliver.geometry(app).scroll_extent;
1677                    current = self.child_before(app, sliver);
1678                }
1679                scroll_offset_to_child - scroll_offset_within_child
1680            }
1681        }
1682    }
1683
1684    fn max_scroll_obstruction_extent_before(
1685        self: RenderHandle<Self>,
1686        app: &App,
1687        child: AnyRenderSliver,
1688    ) -> f64 {
1689        debug_assert_eq!(child.as_object().parent(app), Some(self.as_object()));
1690        let mut pinned_extent = 0.0;
1691        match child.constraints(app).growth_direction {
1692            GrowthDirection::Forward => {
1693                let mut current = self.center(app);
1694                while current != Some(child) {
1695                    let sliver = current.expect("the center precedes a forward child");
1696                    pinned_extent += sliver.geometry(app).max_scroll_obstruction_extent;
1697                    current = self.child_after(app, sliver);
1698                }
1699            }
1700            GrowthDirection::Reverse => {
1701                let center = self.center(app).expect("a laid-out viewport has a center");
1702                let mut current = self.child_before(app, center);
1703                while current != Some(child) {
1704                    let sliver = current.expect("a reverse child precedes the center");
1705                    pinned_extent += sliver.geometry(app).max_scroll_obstruction_extent;
1706                    current = self.child_before(app, sliver);
1707                }
1708            }
1709        }
1710        pinned_extent
1711    }
1712
1713    fn compute_child_main_axis_position(
1714        self: RenderHandle<Self>,
1715        app: &App,
1716        child: AnyRenderSliver,
1717        parent_main_axis_position: f64,
1718    ) -> f64 {
1719        let paint_offset = child
1720            .as_object()
1721            .parent_data_of::<SliverPhysicalContainerParentData>(app)
1722            .paint_offset();
1723        let constraints = child.constraints(app);
1724        let paint_extent = child.geometry(app).paint_extent;
1725        match apply_growth_direction_to_axis_direction(
1726            constraints.axis_direction,
1727            constraints.growth_direction,
1728        ) {
1729            AxisDirection::Down => parent_main_axis_position - paint_offset.dy(),
1730            AxisDirection::Right => parent_main_axis_position - paint_offset.dx(),
1731            AxisDirection::Up => paint_extent - (parent_main_axis_position - paint_offset.dy()),
1732            AxisDirection::Left => paint_extent - (parent_main_axis_position - paint_offset.dx()),
1733        }
1734    }
1735
1736    fn index_of_first_child(self: RenderHandle<Self>, app: &App) -> i32 {
1737        let center = self.center(app).expect("a laid-out viewport has a center");
1738        debug_assert_eq!(center.as_object().parent(app), Some(self.as_object()));
1739        debug_assert!(self.first_child(app).is_some());
1740        let mut count = 0;
1741        let mut child = Some(center);
1742        while child != self.first_child(app) {
1743            count -= 1;
1744            child = self.child_before(app, child.expect("the first child terminates the walk"));
1745        }
1746        count
1747    }
1748
1749    fn label_for_child(self: RenderHandle<Self>, index: i32) -> String {
1750        if index == 0 {
1751            "center child".to_owned()
1752        } else {
1753            format!("child {index}")
1754        }
1755    }
1756}
1757
1758impl RenderAbstractViewport for RenderViewport {
1759    fn get_offset_to_reveal(
1760        self: RenderHandle<Self>,
1761        app: &App,
1762        target: AnyRenderObject,
1763        alignment: f64,
1764        rect: Option<Rect>,
1765        axis: Option<Axis>,
1766    ) -> RevealedOffset {
1767        RenderViewportBase::get_offset_to_reveal(self, app, target, alignment, rect, axis)
1768    }
1769}
1770
1771impl RenderObject for RenderViewport {
1772    crate::render_object_accessors!();
1773
1774    fn visit_children(
1775        self: RenderHandle<Self>,
1776        app: &App,
1777        visitor: &mut dyn FnMut(AnyRenderObject),
1778    ) {
1779        ContainerRenderObjectMixin::visit_children(self, app, visitor)
1780    }
1781
1782    fn did_attach(
1783        self: RenderHandle<Self>,
1784        app: &mut App,
1785        owner: inset_foundation::Handle<PipelineOwner>,
1786    ) {
1787        RenderViewportBase::did_attach(self, app, owner)
1788    }
1789
1790    fn did_detach(self: RenderHandle<Self>, app: &mut App) {
1791        RenderViewportBase::did_detach(self, app)
1792    }
1793
1794    fn redepth_children(self: RenderHandle<Self>, app: &mut App) {
1795        ContainerRenderObjectMixin::redepth_children(self, app)
1796    }
1797
1798    fn is_repaint_boundary(self: RenderHandle<Self>, _app: &App) -> bool {
1799        let _ = self;
1800        true
1801    }
1802
1803    fn interface(self: RenderHandle<Self>, id: TypeId) -> Option<Box<dyn Any>> {
1804        (id == TypeId::of::<AnyRenderAbstractViewport>())
1805            .then(|| Box::new(self.as_abstract_viewport()) as Box<dyn Any>)
1806    }
1807
1808    fn sized_by_parent(self: RenderHandle<Self>, _app: &App) -> bool {
1809        let _ = self;
1810        true
1811    }
1812
1813    fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
1814        // Ignore the return value of apply_viewport_dimension because we are doing a layout
1815        // regardless.
1816        let offset = self.offset(app);
1817        let size = self.size(app);
1818        match self.axis(app) {
1819            Axis::Vertical => offset.apply_viewport_dimension(app, size.height()),
1820            Axis::Horizontal => offset.apply_viewport_dimension(app, size.width()),
1821        };
1822
1823        let Some(center) = self.center(app) else {
1824            debug_assert!(self.first_child(app).is_none());
1825            let this = self.get_mut(app);
1826            this.min_scroll_extent = 0.0;
1827            this.max_scroll_extent = 0.0;
1828            this.has_visual_overflow = false;
1829            offset.apply_content_dimensions(app, 0.0, 0.0);
1830            return;
1831        };
1832        debug_assert_eq!(center.as_object().parent(app), Some(self.as_object()));
1833
1834        let (main_axis_extent, cross_axis_extent) = match self.axis(app) {
1835            Axis::Vertical => (size.height(), size.width()),
1836            Axis::Horizontal => (size.width(), size.height()),
1837        };
1838
1839        let center_offset_adjustment = center.center_offset_adjustment(app);
1840        let max_layout_cycles = RenderViewport::MAX_LAYOUT_CYCLES_PER_CHILD * self.child_count(app);
1841
1842        let anchor = self.get(app).anchor;
1843        let mut count = 0;
1844        loop {
1845            let correction = self.attempt_layout(
1846                app,
1847                main_axis_extent,
1848                cross_axis_extent,
1849                offset.pixels(app) + center_offset_adjustment,
1850            );
1851            if correction != 0.0 {
1852                offset.correct_by(app, correction);
1853            } else {
1854                let min_scroll_extent = self.get(app).min_scroll_extent;
1855                let max_scroll_extent = self.get(app).max_scroll_extent;
1856                if offset.apply_content_dimensions(
1857                    app,
1858                    (min_scroll_extent + main_axis_extent * anchor).min(0.0),
1859                    (max_scroll_extent - main_axis_extent * (1.0 - anchor)).max(0.0),
1860                ) {
1861                    break;
1862                }
1863            }
1864            count += 1;
1865            if count >= max_layout_cycles {
1866                break;
1867            }
1868        }
1869        debug_assert!(
1870            count < max_layout_cycles,
1871            "A RenderViewport exceeded its maximum number of layout cycles.\n\
1872             RenderViewport render objects, during layout, can retry if either their slivers or \
1873             their ViewportOffset decide that the offset should be corrected to take into \
1874             account information collected during that layout.\n\
1875             In the case of this RenderViewport object, however, this happened {count} times and \
1876             still there was no consensus on the scroll offset. This usually indicates a bug."
1877        );
1878    }
1879
1880    fn paint(
1881        self: RenderHandle<Self>,
1882        app: &mut App,
1883        context: &mut PaintingContext,
1884        offset: Offset,
1885    ) {
1886        RenderViewportBase::paint(self, app, context, offset)
1887    }
1888
1889    fn dispose(self: RenderHandle<Self>, app: &mut App) {
1890        RenderViewportBase::dispose(self, app)
1891    }
1892
1893    fn show_on_screen(
1894        self: RenderHandle<Self>,
1895        app: &mut App,
1896        descendant: Option<AnyRenderObject>,
1897        rect: Option<Rect>,
1898        duration: Duration,
1899        curve: Rc<dyn Curve>,
1900    ) {
1901        RenderViewportBase::show_on_screen(self, app, descendant, rect, duration, curve)
1902    }
1903}
1904
1905impl RenderBox for RenderViewport {
1906    crate::render_box_accessors!();
1907
1908    fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
1909        if !child.parent_data_is::<SliverPhysicalContainerParentData>(app) {
1910            child.set_parent_data(app, SliverPhysicalContainerParentData::new());
1911        }
1912    }
1913
1914    fn apply_paint_transform(
1915        self: RenderHandle<Self>,
1916        app: &App,
1917        child: AnyRenderObject,
1918        transform: &mut Matrix4,
1919    ) {
1920        // Hit test logic relies on this always providing an invertible matrix.
1921        child
1922            .parent_data_of::<SliverPhysicalContainerParentData>(app)
1923            .apply_paint_transform(transform);
1924    }
1925
1926    fn hit_test_children(
1927        self: RenderHandle<Self>,
1928        app: &mut App,
1929        result: &mut BoxHitTestResult<'_>,
1930        position: Offset,
1931    ) -> bool {
1932        RenderViewportBase::hit_test_children(self, app, result, position)
1933    }
1934
1935    fn compute_min_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
1936        RenderViewportBase::compute_min_intrinsic_width(self, app, height)
1937    }
1938
1939    fn compute_max_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
1940        RenderViewportBase::compute_max_intrinsic_width(self, app, height)
1941    }
1942
1943    fn compute_min_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
1944        RenderViewportBase::compute_min_intrinsic_height(self, app, width)
1945    }
1946
1947    fn compute_max_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
1948        RenderViewportBase::compute_max_intrinsic_height(self, app, width)
1949    }
1950
1951    fn compute_dry_layout(
1952        self: RenderHandle<Self>,
1953        app: &mut App,
1954        constraints: BoxConstraints,
1955    ) -> Size {
1956        debug_assert!(debug_check_has_bounded_axis(self.axis(app), constraints));
1957        constraints.biggest()
1958    }
1959}
1960
1961/// A render object that is bigger on the inside and shrink wraps its children in the main axis.
1962///
1963/// [`RenderShrinkWrappingViewport`] displays a subset of its children according to its own
1964/// dimensions and the given [`RenderViewportBase::offset`]. As the offset varies, different
1965/// children are visible through the viewport.
1966///
1967/// It differs from [`RenderViewport`] in that [`RenderViewport`] expands to fill the main axis
1968/// whereas this shrink-wraps itself to match its children in the main axis. This shrink wrapping
1969/// behavior is expensive because the children, and hence the viewport, could potentially change
1970/// size whenever the offset changes (e.g., because of a collapsing header).
1971///
1972/// [`RenderShrinkWrappingViewport`] cannot contain [`RenderBox`] children directly. Instead, use
1973/// a [`crate::RenderSliverList`], [`crate::RenderSliverFixedExtentList`], or a
1974/// [`crate::RenderSliverToBoxAdapter`], for example.
1975pub struct RenderShrinkWrappingViewport {
1976    render_object: RenderObjectData,
1977    render_box: RenderBoxData,
1978    container: ContainerRenderObjectData<AnyRenderSliver>,
1979    viewport: RenderViewportBaseData,
1980    // Out-of-band data computed during layout.
1981    max_scroll_extent: f64,
1982    shrink_wrap_extent: f64,
1983    has_visual_overflow: bool,
1984}
1985
1986impl RenderShrinkWrappingViewport {
1987    /// Creates a viewport (for [`AnyRenderSliver`] objects) that shrink-wraps its contents.
1988    ///
1989    /// For testing purposes, consider passing a [`crate::FixedViewportOffset`] as the `offset`.
1990    pub fn new(
1991        app: &mut App,
1992        cross_axis_direction: AxisDirection,
1993        offset: AnyViewportOffset,
1994        children: Option<Vec<AnyRenderSliver>>,
1995    ) -> RenderHandle<RenderShrinkWrappingViewport> {
1996        let this = RenderHandle::new_box(
1997            app,
1998            RenderShrinkWrappingViewport {
1999                render_object: RenderObjectData::new(),
2000                render_box: RenderBoxData::new(),
2001                container: ContainerRenderObjectData::new(),
2002                viewport: RenderViewportBaseData::new(cross_axis_direction, offset),
2003                max_scroll_extent: 0.0,
2004                shrink_wrap_extent: 0.0,
2005                has_visual_overflow: false,
2006            },
2007        );
2008        this.add_all(app, children);
2009        this
2010    }
2011
2012    /// Flutter's `_debugCheckHasBoundedCrossAxis`.
2013    fn debug_check_has_bounded_cross_axis(self: RenderHandle<Self>, app: &App) -> bool {
2014        let constraints = self.constraints(app);
2015        match self.axis(app) {
2016            Axis::Vertical => debug_assert!(
2017                constraints.has_bounded_width(),
2018                "Vertical viewport was given unbounded width.\n\
2019                 Viewports expand in the cross axis to fill their container and constrain their \
2020                 children to match their extent in the cross axis. In this case, a vertical \
2021                 shrinkwrapping viewport was given an unlimited amount of horizontal space in \
2022                 which to expand."
2023            ),
2024            Axis::Horizontal => debug_assert!(
2025                constraints.has_bounded_height(),
2026                "Horizontal viewport was given unbounded height.\n\
2027                 Viewports expand in the cross axis to fill their container and constrain their \
2028                 children to match their extent in the cross axis. In this case, a horizontal \
2029                 shrinkwrapping viewport was given an unlimited amount of vertical space in \
2030                 which to expand."
2031            ),
2032        }
2033        true
2034    }
2035
2036    /// Flutter's `_attemptLayout`.
2037    fn attempt_layout(
2038        self: RenderHandle<Self>,
2039        app: &mut App,
2040        main_axis_extent: f64,
2041        cross_axis_extent: f64,
2042        corrected_offset: f64,
2043    ) -> f64 {
2044        // We can't assert main_axis_extent is finite, because it could be infinite if it is
2045        // within a column or row for example. In such a case, there's not even any scrolling to
2046        // do, although some scroll physics could still temporarily scroll the content in a
2047        // simulation.
2048        debug_assert!(!main_axis_extent.is_nan());
2049        debug_assert!(main_axis_extent >= 0.0);
2050        debug_assert!(cross_axis_extent.is_finite());
2051        debug_assert!(cross_axis_extent >= 0.0);
2052        debug_assert!(corrected_offset.is_finite());
2053        let this = self.get_mut(app);
2054        this.max_scroll_extent = 0.0;
2055        this.shrink_wrap_extent = 0.0;
2056        // Since the viewport is shrinkwrapped, we know that any negative overscroll into the
2057        // potentially infinite main_axis_extent will overflow the end of the viewport.
2058        this.has_visual_overflow = corrected_offset < 0.0;
2059        let calculated_cache_extent = if main_axis_extent.is_finite() {
2060            self.scroll_cache_extent(app)
2061                .calculate_cache_offset(main_axis_extent)
2062        } else {
2063            // If main_axis_extent is infinite, it builds everything anyway, so we don't need any
2064            // extra cache.
2065            0.0
2066        };
2067        self.viewport_data_mut(app).calculated_cache_extent = Some(calculated_cache_extent);
2068
2069        let first_child = self.first_child(app);
2070        self.layout_child_sequence(
2071            app,
2072            first_child,
2073            corrected_offset.max(0.0),
2074            corrected_offset.min(0.0),
2075            (-corrected_offset).max(0.0),
2076            main_axis_extent + corrected_offset.min(0.0),
2077            main_axis_extent,
2078            cross_axis_extent,
2079            GrowthDirection::Forward,
2080            &|this, app, child| this.child_after(app, child),
2081            main_axis_extent + 2.0 * calculated_cache_extent,
2082            -calculated_cache_extent,
2083        )
2084    }
2085}
2086
2087impl ContainerRenderObjectMixin for RenderShrinkWrappingViewport {
2088    type ChildType = AnyRenderSliver;
2089    type ParentDataType = SliverLogicalContainerParentData;
2090
2091    fn container_data(
2092        self: RenderHandle<Self>,
2093        app: &App,
2094    ) -> &ContainerRenderObjectData<AnyRenderSliver> {
2095        &self.get(app).container
2096    }
2097
2098    fn container_data_mut(
2099        self: RenderHandle<Self>,
2100        app: &mut App,
2101    ) -> &mut ContainerRenderObjectData<AnyRenderSliver> {
2102        &mut self.get_mut(app).container
2103    }
2104}
2105
2106impl RenderViewportBase for RenderShrinkWrappingViewport {
2107    crate::render_viewport_base_accessors!();
2108
2109    fn debug_throw_if_not_checking_intrinsics(self: RenderHandle<Self>) -> bool {
2110        let _ = self;
2111        debug_assert!(
2112            debug_checking_intrinsics(),
2113            "{} does not support returning intrinsic dimensions.\n\
2114             Calculating the intrinsic dimensions would require instantiating every child of \
2115             the viewport, which defeats the point of viewports being lazy.\n\
2116             If you are merely trying to shrink-wrap the viewport in the main axis direction, \
2117             you should be able to achieve that effect by just giving the viewport loose \
2118             constraints, without needing to measure its intrinsic dimensions.",
2119            std::any::type_name::<Self>()
2120        );
2121        true
2122    }
2123
2124    fn has_visual_overflow(self: RenderHandle<Self>, app: &App) -> bool {
2125        self.get(app).has_visual_overflow
2126    }
2127
2128    fn update_out_of_band_data(
2129        self: RenderHandle<Self>,
2130        app: &mut App,
2131        growth_direction: GrowthDirection,
2132        child_layout_geometry: SliverGeometry,
2133    ) {
2134        debug_assert_eq!(growth_direction, GrowthDirection::Forward);
2135        let this = self.get_mut(app);
2136        this.max_scroll_extent += child_layout_geometry.scroll_extent;
2137        if child_layout_geometry.has_visual_overflow {
2138            this.has_visual_overflow = true;
2139        }
2140        this.shrink_wrap_extent += child_layout_geometry.max_paint_extent;
2141    }
2142
2143    fn update_child_layout_offset(
2144        self: RenderHandle<Self>,
2145        app: &mut App,
2146        child: AnyRenderSliver,
2147        layout_offset: f64,
2148        growth_direction: GrowthDirection,
2149    ) {
2150        debug_assert_eq!(growth_direction, GrowthDirection::Forward);
2151        child
2152            .as_object()
2153            .parent_data_of_mut::<SliverLogicalContainerParentData>(app)
2154            .set_layout_offset(Some(layout_offset));
2155    }
2156
2157    fn paint_offset_of(self: RenderHandle<Self>, app: &App, child: AnyRenderSliver) -> Offset {
2158        let layout_offset = child
2159            .as_object()
2160            .parent_data_of::<SliverLogicalContainerParentData>(app)
2161            .layout_offset()
2162            .expect("a laid-out child has a layout offset");
2163        self.compute_absolute_paint_offset(app, child, layout_offset, GrowthDirection::Forward)
2164    }
2165
2166    fn scroll_offset_of(
2167        self: RenderHandle<Self>,
2168        app: &App,
2169        child: AnyRenderSliver,
2170        scroll_offset_within_child: f64,
2171    ) -> f64 {
2172        debug_assert_eq!(child.as_object().parent(app), Some(self.as_object()));
2173        debug_assert_eq!(
2174            child.constraints(app).growth_direction,
2175            GrowthDirection::Forward
2176        );
2177        let mut scroll_offset_to_child = 0.0;
2178        let mut current = self.first_child(app);
2179        while current != Some(child) {
2180            let sliver = current.expect("child is in the child list");
2181            scroll_offset_to_child += sliver.geometry(app).scroll_extent;
2182            current = self.child_after(app, sliver);
2183        }
2184        scroll_offset_to_child + scroll_offset_within_child
2185    }
2186
2187    fn max_scroll_obstruction_extent_before(
2188        self: RenderHandle<Self>,
2189        app: &App,
2190        child: AnyRenderSliver,
2191    ) -> f64 {
2192        debug_assert_eq!(child.as_object().parent(app), Some(self.as_object()));
2193        debug_assert_eq!(
2194            child.constraints(app).growth_direction,
2195            GrowthDirection::Forward
2196        );
2197        let mut pinned_extent = 0.0;
2198        let mut current = self.first_child(app);
2199        while current != Some(child) {
2200            let sliver = current.expect("child is in the child list");
2201            pinned_extent += sliver.geometry(app).max_scroll_obstruction_extent;
2202            current = self.child_after(app, sliver);
2203        }
2204        pinned_extent
2205    }
2206
2207    fn compute_child_main_axis_position(
2208        self: RenderHandle<Self>,
2209        app: &App,
2210        child: AnyRenderSliver,
2211        parent_main_axis_position: f64,
2212    ) -> f64 {
2213        debug_assert!(self.has_size(app));
2214        let layout_offset = child
2215            .as_object()
2216            .parent_data_of::<SliverLogicalContainerParentData>(app)
2217            .layout_offset()
2218            .expect("a laid-out child has a layout offset");
2219        let constraints = child.constraints(app);
2220        let size = self.size(app);
2221        match apply_growth_direction_to_axis_direction(
2222            constraints.axis_direction,
2223            constraints.growth_direction,
2224        ) {
2225            AxisDirection::Down | AxisDirection::Right => parent_main_axis_position - layout_offset,
2226            AxisDirection::Up => size.height() - parent_main_axis_position - layout_offset,
2227            AxisDirection::Left => size.width() - parent_main_axis_position - layout_offset,
2228        }
2229    }
2230
2231    fn index_of_first_child(self: RenderHandle<Self>, _app: &App) -> i32 {
2232        let _ = self;
2233        0
2234    }
2235
2236    fn label_for_child(self: RenderHandle<Self>, index: i32) -> String {
2237        let _ = self;
2238        format!("child {index}")
2239    }
2240}
2241
2242impl RenderAbstractViewport for RenderShrinkWrappingViewport {
2243    fn get_offset_to_reveal(
2244        self: RenderHandle<Self>,
2245        app: &App,
2246        target: AnyRenderObject,
2247        alignment: f64,
2248        rect: Option<Rect>,
2249        axis: Option<Axis>,
2250    ) -> RevealedOffset {
2251        RenderViewportBase::get_offset_to_reveal(self, app, target, alignment, rect, axis)
2252    }
2253}
2254
2255impl RenderObject for RenderShrinkWrappingViewport {
2256    crate::render_object_accessors!();
2257
2258    fn visit_children(
2259        self: RenderHandle<Self>,
2260        app: &App,
2261        visitor: &mut dyn FnMut(AnyRenderObject),
2262    ) {
2263        ContainerRenderObjectMixin::visit_children(self, app, visitor)
2264    }
2265
2266    fn did_attach(
2267        self: RenderHandle<Self>,
2268        app: &mut App,
2269        owner: inset_foundation::Handle<PipelineOwner>,
2270    ) {
2271        RenderViewportBase::did_attach(self, app, owner)
2272    }
2273
2274    fn did_detach(self: RenderHandle<Self>, app: &mut App) {
2275        RenderViewportBase::did_detach(self, app)
2276    }
2277
2278    fn redepth_children(self: RenderHandle<Self>, app: &mut App) {
2279        ContainerRenderObjectMixin::redepth_children(self, app)
2280    }
2281
2282    fn is_repaint_boundary(self: RenderHandle<Self>, _app: &App) -> bool {
2283        let _ = self;
2284        true
2285    }
2286
2287    fn interface(self: RenderHandle<Self>, id: TypeId) -> Option<Box<dyn Any>> {
2288        (id == TypeId::of::<AnyRenderAbstractViewport>())
2289            .then(|| Box::new(self.as_abstract_viewport()) as Box<dyn Any>)
2290    }
2291
2292    fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
2293        let constraints = self.constraints(app);
2294        let offset = self.offset(app);
2295        if self.first_child(app).is_none() {
2296            // Shrinkwrapping viewport only requires the cross axis to be bounded.
2297            debug_assert!(self.debug_check_has_bounded_cross_axis(app));
2298            let size = match self.axis(app) {
2299                Axis::Vertical => Size::new(constraints.max_width, constraints.min_height),
2300                Axis::Horizontal => Size::new(constraints.min_width, constraints.max_height),
2301            };
2302            self.set_size(app, size);
2303            offset.apply_viewport_dimension(app, 0.0);
2304            let this = self.get_mut(app);
2305            this.max_scroll_extent = 0.0;
2306            this.shrink_wrap_extent = 0.0;
2307            this.has_visual_overflow = false;
2308            offset.apply_content_dimensions(app, 0.0, 0.0);
2309            return;
2310        }
2311
2312        // Shrinkwrapping viewport only requires the cross axis to be bounded.
2313        debug_assert!(self.debug_check_has_bounded_cross_axis(app));
2314        let (main_axis_extent, cross_axis_extent) = match self.axis(app) {
2315            Axis::Vertical => (constraints.max_height, constraints.max_width),
2316            Axis::Horizontal => (constraints.max_width, constraints.max_height),
2317        };
2318
2319        let effective_extent;
2320        loop {
2321            let corrected_offset = offset.pixels(app);
2322            let correction =
2323                self.attempt_layout(app, main_axis_extent, cross_axis_extent, corrected_offset);
2324            if correction != 0.0 {
2325                offset.correct_by(app, correction);
2326                continue;
2327            }
2328            let shrink_wrap_extent = self.get(app).shrink_wrap_extent;
2329            let extent = match self.axis(app) {
2330                Axis::Vertical => constraints.constrain_height(shrink_wrap_extent),
2331                Axis::Horizontal => constraints.constrain_width(shrink_wrap_extent),
2332            };
2333            let did_accept_viewport_dimension = offset.apply_viewport_dimension(app, extent);
2334            let max_scroll_extent = self.get(app).max_scroll_extent;
2335            let did_accept_content_dimension =
2336                offset.apply_content_dimensions(app, 0.0, (max_scroll_extent - extent).max(0.0));
2337            if did_accept_viewport_dimension && did_accept_content_dimension {
2338                effective_extent = extent;
2339                break;
2340            }
2341        }
2342        let size = match self.axis(app) {
2343            Axis::Vertical => constraints.constrain_dimensions(cross_axis_extent, effective_extent),
2344            Axis::Horizontal => {
2345                constraints.constrain_dimensions(effective_extent, cross_axis_extent)
2346            }
2347        };
2348        self.set_size(app, size);
2349    }
2350
2351    fn paint(
2352        self: RenderHandle<Self>,
2353        app: &mut App,
2354        context: &mut PaintingContext,
2355        offset: Offset,
2356    ) {
2357        RenderViewportBase::paint(self, app, context, offset)
2358    }
2359
2360    fn dispose(self: RenderHandle<Self>, app: &mut App) {
2361        RenderViewportBase::dispose(self, app)
2362    }
2363
2364    fn show_on_screen(
2365        self: RenderHandle<Self>,
2366        app: &mut App,
2367        descendant: Option<AnyRenderObject>,
2368        rect: Option<Rect>,
2369        duration: Duration,
2370        curve: Rc<dyn Curve>,
2371    ) {
2372        RenderViewportBase::show_on_screen(self, app, descendant, rect, duration, curve)
2373    }
2374}
2375
2376impl RenderBox for RenderShrinkWrappingViewport {
2377    crate::render_box_accessors!();
2378
2379    fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
2380        if !child.parent_data_is::<SliverLogicalContainerParentData>(app) {
2381            child.set_parent_data(app, SliverLogicalContainerParentData::new());
2382        }
2383    }
2384
2385    fn apply_paint_transform(
2386        self: RenderHandle<Self>,
2387        app: &App,
2388        child: AnyRenderObject,
2389        transform: &mut Matrix4,
2390    ) {
2391        // Hit test logic relies on this always providing an invertible matrix.
2392        let offset = self.paint_offset_of(
2393            app,
2394            child
2395                .as_sliver()
2396                .expect("a viewport's children are slivers"),
2397        );
2398        translate(transform, offset.dx(), offset.dy());
2399    }
2400
2401    fn hit_test_children(
2402        self: RenderHandle<Self>,
2403        app: &mut App,
2404        result: &mut BoxHitTestResult<'_>,
2405        position: Offset,
2406    ) -> bool {
2407        RenderViewportBase::hit_test_children(self, app, result, position)
2408    }
2409
2410    fn compute_min_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
2411        RenderViewportBase::compute_min_intrinsic_width(self, app, height)
2412    }
2413
2414    fn compute_max_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
2415        RenderViewportBase::compute_max_intrinsic_width(self, app, height)
2416    }
2417
2418    fn compute_min_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
2419        RenderViewportBase::compute_min_intrinsic_height(self, app, width)
2420    }
2421
2422    fn compute_max_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
2423        RenderViewportBase::compute_max_intrinsic_height(self, app, width)
2424    }
2425}
2426
2427#[cfg(test)]
2428mod tests {
2429    use inset_foundation::AppCell;
2430    use std::cell::Cell;
2431    use std::rc::Rc;
2432
2433    use inset_foundation::Handle;
2434    use inset_gestures::HitTestResult;
2435
2436    use super::*;
2437    use crate::box_::BoxConstraints;
2438    use crate::layer::{ContainerLayer, ErasedLayer, OffsetLayer};
2439    use crate::proxy_box::RenderConstrainedBox;
2440    use crate::sliver::{RenderSliver, RenderSliverToBoxAdapter};
2441    use crate::viewport_offset::{FixedViewportOffset, ViewportOffset};
2442
2443    /// A sliver of a fixed scroll extent that records the offsets it was painted at.
2444    struct TestSliver {
2445        render_object: RenderObjectData,
2446        render_sliver: crate::sliver::RenderSliverData,
2447        extent: f64,
2448        paint_offsets: Rc<Cell<Vec<Offset>>>,
2449    }
2450
2451    impl RenderObject for TestSliver {
2452        crate::render_object_accessors!();
2453
2454        fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
2455            let constraints = self.constraints(app);
2456            let extent = self.get(app).extent;
2457            let painted = self.calculate_paint_offset(app, constraints, 0.0, extent);
2458            let cached = self.calculate_cache_offset(app, constraints, 0.0, extent);
2459            self.set_geometry(
2460                app,
2461                SliverGeometry::new()
2462                    .scroll_extent(extent)
2463                    .paint_extent(painted)
2464                    .cache_extent(cached)
2465                    .max_paint_extent(extent),
2466            );
2467        }
2468
2469        fn paint(
2470            self: RenderHandle<Self>,
2471            app: &mut App,
2472            _context: &mut PaintingContext,
2473            offset: Offset,
2474        ) {
2475            let recorded = self.get(app).paint_offsets.clone();
2476            let mut offsets = recorded.take();
2477            offsets.push(offset);
2478            recorded.set(offsets);
2479        }
2480    }
2481
2482    impl RenderSliver for TestSliver {
2483        crate::render_sliver_accessors!();
2484
2485        fn hit_test_self(
2486            self: RenderHandle<Self>,
2487            _app: &App,
2488            _main_axis_position: f64,
2489            _cross_axis_position: f64,
2490        ) -> bool {
2491            let _ = self;
2492            true
2493        }
2494    }
2495
2496    fn test_sliver(app: &mut App, extent: f64) -> (AnyRenderSliver, Rc<Cell<Vec<Offset>>>) {
2497        let paint_offsets = Rc::new(Cell::new(Vec::new()));
2498        let sliver = RenderHandle::new_sliver(
2499            app,
2500            TestSliver {
2501                render_object: RenderObjectData::new(),
2502                render_sliver: crate::sliver::RenderSliverData::new(),
2503                extent,
2504                paint_offsets: paint_offsets.clone(),
2505            },
2506        )
2507        .as_sliver();
2508        (sliver, paint_offsets)
2509    }
2510
2511    /// The test binding's first frame: root the tree, lay it out, and flush paint.
2512    fn first_frame(
2513        app: &mut App,
2514        root: crate::box_::AnyRenderBox,
2515        constraints: BoxConstraints,
2516    ) -> Handle<PipelineOwner> {
2517        let owner = PipelineOwner::new(app, None, None);
2518        owner.set_root_node(app, Some(root.as_object()));
2519        root.layout(app, constraints, false);
2520        let paint_root = OffsetLayer::new(app, Offset::ZERO);
2521        paint_root.as_layer().attach(app, root.as_object().id());
2522        root.as_object()
2523            .schedule_initial_paint(app, paint_root.as_container_layer());
2524        owner.flush_compositing_bits(app);
2525        owner.flush_paint(app);
2526        owner
2527    }
2528
2529    fn vertical_viewport(
2530        app: &mut App,
2531        offset: Handle<FixedViewportOffset>,
2532        children: Vec<AnyRenderSliver>,
2533    ) -> RenderHandle<RenderViewport> {
2534        RenderViewport::new(
2535            app,
2536            AxisDirection::Right,
2537            offset.as_viewport_offset(),
2538            Some(children),
2539            None,
2540        )
2541    }
2542
2543    /// `viewport_test.dart`: a viewport does not support intrinsics, because answering would
2544    /// mean instantiating every child.
2545    #[test]
2546    #[should_panic(expected = "does not support returning intrinsic dimensions")]
2547    fn a_viewport_has_no_intrinsic_dimensions() {
2548        let cell = AppCell::new();
2549        let mut app = cell.borrow_mut();
2550        let offset = FixedViewportOffset::zero(&mut app);
2551        let viewport = vertical_viewport(&mut app, offset, vec![]);
2552        viewport.as_box().get_min_intrinsic_width(&mut app, 0.0);
2553    }
2554
2555    /// While the framework is checking intrinsics, a viewport answers zero instead of panicking.
2556    #[test]
2557    fn a_viewport_reports_zero_intrinsics_while_checking_them() {
2558        let cell = AppCell::new();
2559        let mut app = cell.borrow_mut();
2560        let offset = FixedViewportOffset::zero(&mut app);
2561        let viewport = vertical_viewport(&mut app, offset, vec![]);
2562        crate::object::set_debug_checking_intrinsics(true);
2563        let width = viewport.as_box().get_min_intrinsic_width(&mut app, 0.0);
2564        let height = viewport.as_box().get_max_intrinsic_height(&mut app, 0.0);
2565        crate::object::set_debug_checking_intrinsics(false);
2566        assert_eq!((width, height), (0.0, 0.0));
2567    }
2568
2569    /// A viewport is sized by its parent: its dry layout is the biggest size the constraints
2570    /// allow, and that is the size it takes.
2571    #[test]
2572    fn a_viewport_dry_layout_is_the_biggest_size() {
2573        let cell = AppCell::new();
2574        let mut app = cell.borrow_mut();
2575        let (sliver, _paints) = test_sliver(&mut app, 100.0);
2576        let offset = FixedViewportOffset::zero(&mut app);
2577        let viewport = vertical_viewport(&mut app, offset, vec![sliver]);
2578        let constraints = BoxConstraints::new().max_width(200.0).max_height(400.0);
2579        let dry = viewport.as_box().get_dry_layout(&mut app, constraints);
2580        first_frame(&mut app, viewport.as_box(), constraints);
2581        assert_eq!(dry, Size::new(200.0, 400.0));
2582        assert_eq!(dry, viewport.size(&app));
2583    }
2584
2585    /// `viewport_test.dart`: the slivers are laid out one after the other from the leading edge,
2586    /// and the viewport reports the total scroll extent to its offset.
2587    #[test]
2588    fn a_viewport_lays_its_slivers_out_in_order() {
2589        let cell = AppCell::new();
2590        let mut app = cell.borrow_mut();
2591        let (first, first_paints) = test_sliver(&mut app, 100.0);
2592        let (second, second_paints) = test_sliver(&mut app, 300.0);
2593        let offset = FixedViewportOffset::zero(&mut app);
2594        let viewport = vertical_viewport(&mut app, offset, vec![first, second]);
2595
2596        first_frame(
2597            &mut app,
2598            viewport.as_box(),
2599            BoxConstraints::tight(Size::new(200.0, 400.0)),
2600        );
2601
2602        assert_eq!(viewport.size(&app), Size::new(200.0, 400.0));
2603        assert_eq!(viewport.center(&app), Some(first));
2604        assert_eq!(first.geometry(&app).paint_extent, 100.0);
2605        assert_eq!(second.geometry(&app).paint_extent, 300.0);
2606        assert_eq!(viewport.paint_offset_of(&app, first), Offset::ZERO);
2607        assert_eq!(
2608            viewport.paint_offset_of(&app, second),
2609            Offset::new(0.0, 100.0)
2610        );
2611        assert_eq!(first_paints.take(), vec![Offset::ZERO]);
2612        assert_eq!(second_paints.take(), vec![Offset::new(0.0, 100.0)]);
2613    }
2614
2615    /// A non-zero offset scrolls the content: the first sliver is partly scrolled off and the
2616    /// second moves up by the same amount.
2617    #[test]
2618    fn a_scrolled_viewport_moves_its_slivers() {
2619        let cell = AppCell::new();
2620        let mut app = cell.borrow_mut();
2621        let (first, _) = test_sliver(&mut app, 100.0);
2622        let (second, _) = test_sliver(&mut app, 300.0);
2623        let offset = FixedViewportOffset::new(&mut app, 60.0);
2624        let viewport = vertical_viewport(&mut app, offset, vec![first, second]);
2625
2626        first_frame(
2627            &mut app,
2628            viewport.as_box(),
2629            BoxConstraints::tight(Size::new(200.0, 400.0)),
2630        );
2631
2632        assert_eq!(first.constraints(&app).scroll_offset, 60.0);
2633        assert_eq!(first.geometry(&app).paint_extent, 40.0);
2634        assert_eq!(viewport.paint_offset_of(&app, first), Offset::ZERO);
2635        assert_eq!(
2636            viewport.paint_offset_of(&app, second),
2637            Offset::new(0.0, 40.0)
2638        );
2639        assert_eq!(viewport.scroll_offset_of(&app, second, 0.0), 100.0);
2640    }
2641
2642    /// A hit lands on the sliver that covers the position, in the viewport's coordinate system.
2643    #[test]
2644    fn a_viewport_hit_tests_the_sliver_under_the_position() {
2645        let cell = AppCell::new();
2646        let mut app = cell.borrow_mut();
2647        let (first, _) = test_sliver(&mut app, 100.0);
2648        let (second, _) = test_sliver(&mut app, 300.0);
2649        let offset = FixedViewportOffset::zero(&mut app);
2650        let viewport = vertical_viewport(&mut app, offset, vec![first, second]);
2651        first_frame(
2652            &mut app,
2653            viewport.as_box(),
2654            BoxConstraints::tight(Size::new(200.0, 400.0)),
2655        );
2656
2657        let mut result = HitTestResult::new();
2658        let hit = viewport.hit_test(
2659            &mut app,
2660            &mut BoxHitTestResult::wrap(&mut result),
2661            Offset::new(20.0, 150.0),
2662        );
2663
2664        assert!(hit);
2665        assert_eq!(result.path().len(), 2);
2666        // The viewport and the second sliver, which starts at 100.
2667        assert_eq!(
2668            viewport.compute_child_main_axis_position(&app, second, 150.0),
2669            50.0
2670        );
2671    }
2672
2673    /// The cache extent widens the region slivers are asked to lay out for.
2674    #[test]
2675    fn the_cache_extent_widens_the_laid_out_region() {
2676        let cell = AppCell::new();
2677        let mut app = cell.borrow_mut();
2678        let (sliver, _) = test_sliver(&mut app, 1000.0);
2679        let offset = FixedViewportOffset::zero(&mut app);
2680        let viewport = vertical_viewport(&mut app, offset, vec![sliver]);
2681        viewport.set_scroll_cache_extent(&mut app, Some(ScrollCacheExtent::Pixels(100.0)));
2682
2683        first_frame(
2684            &mut app,
2685            viewport.as_box(),
2686            BoxConstraints::tight(Size::new(200.0, 400.0)),
2687        );
2688
2689        assert_eq!(sliver.constraints(&app).remaining_cache_extent, 500.0);
2690        assert_eq!(sliver.geometry(&app).cache_extent, 500.0);
2691        assert_eq!(sliver.geometry(&app).paint_extent, 400.0);
2692
2693        viewport.set_scroll_cache_extent(&mut app, Some(ScrollCacheExtent::Viewport(0.5)));
2694        viewport.as_box().layout(
2695            &mut app,
2696            BoxConstraints::tight(Size::new(200.0, 400.0)),
2697            false,
2698        );
2699        assert_eq!(sliver.constraints(&app).remaining_cache_extent, 600.0);
2700    }
2701
2702    /// `RenderShrinkWrappingViewport` sizes itself to the total extent of its slivers, up to the
2703    /// incoming constraints.
2704    #[test]
2705    fn a_shrink_wrapping_viewport_sizes_itself_to_its_slivers() {
2706        let cell = AppCell::new();
2707        let mut app = cell.borrow_mut();
2708        let (first, _) = test_sliver(&mut app, 60.0);
2709        let (second, _) = test_sliver(&mut app, 90.0);
2710        let offset = FixedViewportOffset::zero(&mut app);
2711        let viewport = RenderShrinkWrappingViewport::new(
2712            &mut app,
2713            AxisDirection::Right,
2714            offset.as_viewport_offset(),
2715            Some(vec![first, second]),
2716        );
2717
2718        first_frame(
2719            &mut app,
2720            viewport.as_box(),
2721            BoxConstraints::new()
2722                .max_width(200.0)
2723                .min_width(200.0)
2724                .max_height(400.0),
2725        );
2726
2727        assert_eq!(viewport.size(&app), Size::new(200.0, 150.0));
2728        assert_eq!(
2729            viewport.paint_offset_of(&app, second),
2730            Offset::new(0.0, 60.0)
2731        );
2732        assert_eq!(viewport.index_of_first_child(&app), 0);
2733        assert_eq!(viewport.label_for_child(1), "child 1");
2734    }
2735
2736    /// A box adapter inside a viewport reaches the box protocol: the box is laid out with the
2737    /// viewport's cross axis extent and painted at the sliver's paint offset.
2738    #[test]
2739    fn a_viewport_hosts_a_box_through_the_adapter() {
2740        let cell = AppCell::new();
2741        let mut app = cell.borrow_mut();
2742        let child =
2743            RenderConstrainedBox::new(&mut app, BoxConstraints::tight_for(None, Some(80.0)), None);
2744        let adapter = RenderSliverToBoxAdapter::new(&mut app, Some(child.as_box()));
2745        let offset = FixedViewportOffset::zero(&mut app);
2746        let viewport = vertical_viewport(&mut app, offset, vec![adapter.as_sliver()]);
2747
2748        first_frame(
2749            &mut app,
2750            viewport.as_box(),
2751            BoxConstraints::tight(Size::new(200.0, 400.0)),
2752        );
2753
2754        assert_eq!(child.size(&app), Size::new(200.0, 80.0));
2755        assert_eq!(adapter.geometry(&app).scroll_extent, 80.0);
2756        assert_eq!(
2757            child.as_box().local_to_global(&app, Offset::ZERO, None),
2758            Offset::ZERO
2759        );
2760    }
2761
2762    /// The viewport is the `RenderAbstractViewport` that `maybe_of` finds from a descendant.
2763    #[test]
2764    fn maybe_of_finds_the_enclosing_viewport() {
2765        let cell = AppCell::new();
2766        let mut app = cell.borrow_mut();
2767        let child =
2768            RenderConstrainedBox::new(&mut app, BoxConstraints::tight_for(None, Some(80.0)), None);
2769        let adapter = RenderSliverToBoxAdapter::new(&mut app, Some(child.as_box()));
2770        let offset = FixedViewportOffset::zero(&mut app);
2771        let viewport = vertical_viewport(&mut app, offset, vec![adapter.as_sliver()]);
2772        first_frame(
2773            &mut app,
2774            viewport.as_box(),
2775            BoxConstraints::tight(Size::new(200.0, 400.0)),
2776        );
2777
2778        let found = AnyRenderAbstractViewport::maybe_of(&app, Some(child.as_object()));
2779        assert_eq!(found, Some(viewport.as_abstract_viewport()));
2780        assert!(AnyRenderAbstractViewport::maybe_of(&app, None).is_none());
2781    }
2782
2783    /// `get_offset_to_reveal` reports the offset that brings a descendant box to the leading edge.
2784    #[test]
2785    fn get_offset_to_reveal_reports_the_leading_edge_offset() {
2786        let cell = AppCell::new();
2787        let mut app = cell.borrow_mut();
2788        let leading =
2789            RenderConstrainedBox::new(&mut app, BoxConstraints::tight_for(None, Some(100.0)), None);
2790        let first = RenderSliverToBoxAdapter::new(&mut app, Some(leading.as_box()));
2791        let target =
2792            RenderConstrainedBox::new(&mut app, BoxConstraints::tight_for(None, Some(50.0)), None);
2793        let second = RenderSliverToBoxAdapter::new(&mut app, Some(target.as_box()));
2794        let offset = FixedViewportOffset::zero(&mut app);
2795        let viewport = vertical_viewport(
2796            &mut app,
2797            offset,
2798            vec![first.as_sliver(), second.as_sliver()],
2799        );
2800        first_frame(
2801            &mut app,
2802            viewport.as_box(),
2803            BoxConstraints::tight(Size::new(200.0, 400.0)),
2804        );
2805
2806        let revealed = RenderAbstractViewport::get_offset_to_reveal(
2807            viewport,
2808            &app,
2809            target.as_object(),
2810            0.0,
2811            None,
2812            None,
2813        );
2814        assert_eq!(revealed.offset, 100.0);
2815        // At that offset the target sits at the leading edge of the viewport.
2816        assert_eq!(revealed.rect.top, 0.0);
2817        assert_eq!(revealed.rect.bottom, 50.0);
2818    }
2819
2820    /// `RevealedOffset::clamp_offset` picks the nearer edge, and `None` when already visible.
2821    #[test]
2822    fn clamp_offset_picks_the_nearer_edge() {
2823        let leading = RevealedOffset::new(100.0, Rect::ZERO);
2824        let trailing = RevealedOffset::new(20.0, Rect::ZERO);
2825        assert_eq!(
2826            RevealedOffset::clamp_offset(leading, trailing, 200.0).map(|offset| offset.offset),
2827            Some(100.0)
2828        );
2829        assert_eq!(
2830            RevealedOffset::clamp_offset(leading, trailing, 0.0).map(|offset| offset.offset),
2831            Some(20.0)
2832        );
2833        assert!(RevealedOffset::clamp_offset(leading, trailing, 50.0).is_none());
2834    }
2835
2836    /// The paint order decides which sliver paints last and which is hit-tested first.
2837    #[test]
2838    fn the_paint_order_reverses_the_hit_test_order() {
2839        let cell = AppCell::new();
2840        let mut app = cell.borrow_mut();
2841        let (first, _) = test_sliver(&mut app, 100.0);
2842        let (second, _) = test_sliver(&mut app, 300.0);
2843        let offset = FixedViewportOffset::zero(&mut app);
2844        let viewport = vertical_viewport(&mut app, offset, vec![first, second]);
2845        first_frame(
2846            &mut app,
2847            viewport.as_box(),
2848            BoxConstraints::tight(Size::new(200.0, 400.0)),
2849        );
2850
2851        assert_eq!(viewport.children_in_paint_order(&app), vec![second, first]);
2852        assert_eq!(
2853            viewport.children_in_hit_test_order(&app),
2854            vec![first, second]
2855        );
2856
2857        viewport.set_paint_order(&mut app, SliverPaintOrder::LastIsTop);
2858        assert_eq!(viewport.children_in_paint_order(&app), vec![first, second]);
2859        assert_eq!(
2860            viewport.children_in_hit_test_order(&app),
2861            vec![second, first]
2862        );
2863    }
2864}