Skip to main content

inset_rendering/
sliver_grid.rs

1//! Flutter counterpart: `rendering/sliver_grid.dart`.
2
3use std::any::{Any, TypeId};
4use std::fmt::{self, Debug, Display};
5use std::rc::Rc;
6
7use inset_embedder::{Matrix4, Offset};
8use inset_foundation::{App, Handle, PRECISION_ERROR_TOLERANCE};
9use inset_painting::axis_direction_is_reversed;
10
11use crate::box_::{AnyRenderBox, BoxConstraints};
12use crate::object::{
13    AnyRenderObject, ContainerParentData, ContainerParentDataMixin, ContainerRenderObjectData,
14    ContainerRenderObjectMixin, ParentData, RenderHandle, RenderObject, RenderObjectData,
15};
16use crate::painting_context::PaintingContext;
17use crate::pipeline_owner::PipelineOwner;
18use crate::sliver::{
19    RenderSliver, RenderSliverData, RenderSliverHelpers, SliverConstraints, SliverGeometry,
20    SliverHitTestResult, calculate_cache_offset, calculate_paint_offset,
21};
22use crate::sliver_multi_box_adaptor::{
23    RenderSliverBoxChildManager, RenderSliverMultiBoxAdaptor, RenderSliverMultiBoxAdaptorData,
24    RenderSliverWithKeepAliveMixin, SliverMultiBoxAdaptorParentData,
25};
26
27/// Describes the placement of a child in a [`RenderSliverGrid`].
28///
29/// This class is similar to [`Rect`](inset_embedder::Rect), in that it gives a two-dimensional
30/// position and a two-dimensional dimension, but is direction-agnostic.
31///
32/// See also:
33///
34///  * [`SliverGridLayout`], which represents the geometry of all the tiles in a grid.
35///  * [`SliverGridLayout::get_geometry_for_child_index`], which returns this object to describe
36///    the child's placement.
37///  * [`RenderSliverGrid`], which uses this class during its
38///    [`perform_layout`](RenderObject::perform_layout) method.
39#[derive(Clone, Copy)]
40pub struct SliverGridGeometry {
41    /// The scroll offset of the leading edge of the child relative to the leading edge of the
42    /// parent.
43    pub scroll_offset: f64,
44    /// The offset of the child in the non-scrolling axis.
45    ///
46    /// If the scroll axis is vertical, this offset is from the left-most edge of the parent to
47    /// the left-most edge of the child. If the scroll axis is horizontal, this offset is from the
48    /// top-most edge of the parent to the top-most edge of the child.
49    pub cross_axis_offset: f64,
50    /// The extent of the child in the scrolling axis.
51    ///
52    /// If the scroll axis is vertical, this extent is the child's height. If the scroll axis is
53    /// horizontal, this extent is the child's width.
54    pub main_axis_extent: f64,
55    /// The extent of the child in the non-scrolling axis.
56    ///
57    /// If the scroll axis is vertical, this extent is the child's width. If the scroll axis is
58    /// horizontal, this extent is the child's height.
59    pub cross_axis_extent: f64,
60}
61
62impl SliverGridGeometry {
63    /// Creates an object that describes the placement of a child in a [`RenderSliverGrid`].
64    pub const fn new(
65        scroll_offset: f64,
66        cross_axis_offset: f64,
67        main_axis_extent: f64,
68        cross_axis_extent: f64,
69    ) -> SliverGridGeometry {
70        SliverGridGeometry {
71            scroll_offset,
72            cross_axis_offset,
73            main_axis_extent,
74            cross_axis_extent,
75        }
76    }
77
78    /// The scroll offset of the trailing edge of the child relative to the leading edge of the
79    /// parent.
80    pub fn trailing_scroll_offset(&self) -> f64 {
81        self.scroll_offset + self.main_axis_extent
82    }
83
84    /// Returns a tight [`BoxConstraints`] that forces the child to have the required size, given
85    /// a [`SliverConstraints`].
86    pub fn get_box_constraints(&self, constraints: SliverConstraints) -> BoxConstraints {
87        constraints.as_box_constraints(
88            self.main_axis_extent,
89            self.main_axis_extent,
90            Some(self.cross_axis_extent),
91        )
92    }
93}
94
95impl Debug for SliverGridGeometry {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        write!(
98            f,
99            "SliverGridGeometry(scrollOffset: {}, crossAxisOffset: {}, mainAxisExtent: {}, crossAxisExtent: {})",
100            self.scroll_offset,
101            self.cross_axis_offset,
102            self.main_axis_extent,
103            self.cross_axis_extent
104        )
105    }
106}
107
108/// The size and position of all the tiles in a [`RenderSliverGrid`].
109///
110/// Rather that providing a grid with a [`SliverGridLayout`] directly, the grid is provided a
111/// [`SliverGridDelegate`], which computes a [`SliverGridLayout`] given a set of
112/// [`SliverConstraints`]. This allows the algorithm to dynamically respond to changes in the
113/// environment (e.g. the user rotating the device).
114///
115/// The tiles can be placed arbitrarily, but it is more efficient to place tiles roughly in order
116/// by scroll offset because grids reify a contiguous sequence of children.
117///
118/// See also:
119///
120///  * [`SliverGridRegularTileLayout`], which represents a layout that uses equally sized and
121///    spaced tiles.
122///  * [`SliverGridGeometry`], which represents the size and position of a single tile in a grid.
123///  * [`SliverGridDelegate::get_layout`], which returns this object to describe the delegate's
124///    layout.
125///  * [`RenderSliverGrid`], which uses this class during its
126///    [`perform_layout`](RenderObject::perform_layout) method.
127pub trait SliverGridLayout: Debug {
128    /// The minimum child index that intersects with (or is after) this scroll offset.
129    fn get_min_child_index_for_scroll_offset(&self, scroll_offset: f64) -> i32;
130
131    /// The maximum child index that intersects with (or is before) this scroll offset.
132    fn get_max_child_index_for_scroll_offset(&self, scroll_offset: f64) -> i32;
133
134    /// The size and position of the child with the given index.
135    fn get_geometry_for_child_index(&self, index: i32) -> SliverGridGeometry;
136
137    /// The scroll extent needed to fully display all the tiles if there are `child_count`
138    /// children in total.
139    fn compute_max_scroll_offset(&self, child_count: i32) -> f64;
140}
141
142/// A [`SliverGridLayout`] that uses equally sized and spaced tiles.
143///
144/// Rather that providing a grid with a [`SliverGridLayout`] directly, you instead provide the
145/// grid a [`SliverGridDelegate`], which can compute a [`SliverGridLayout`] given the current
146/// [`SliverConstraints`].
147///
148/// This layout is used by [`SliverGridDelegateWithFixedCrossAxisCount`] and
149/// [`SliverGridDelegateWithMaxCrossAxisExtent`].
150#[derive(Clone, Copy, Debug)]
151pub struct SliverGridRegularTileLayout {
152    /// The number of children in the cross axis.
153    pub cross_axis_count: i32,
154    /// The number of pixels from the leading edge of one tile to the leading edge of the next
155    /// tile in the main axis.
156    pub main_axis_stride: f64,
157    /// The number of pixels from the leading edge of one tile to the leading edge of the next
158    /// tile in the cross axis.
159    pub cross_axis_stride: f64,
160    /// The number of pixels from the leading edge of one tile to the trailing edge of the same
161    /// tile in the main axis.
162    pub child_main_axis_extent: f64,
163    /// The number of pixels from the leading edge of one tile to the trailing edge of the same
164    /// tile in the cross axis.
165    pub child_cross_axis_extent: f64,
166    /// Whether the children should be placed in the opposite order of increasing coordinates in
167    /// the cross axis.
168    ///
169    /// For example, if the cross axis is horizontal, the children are placed from left to right
170    /// when [`reverse_cross_axis`](Self::reverse_cross_axis) is false and from right to left when
171    /// it is true.
172    ///
173    /// Typically set to the return value of [`axis_direction_is_reversed`] applied to the
174    /// [`SliverConstraints::cross_axis_direction`].
175    pub reverse_cross_axis: bool,
176}
177
178impl SliverGridRegularTileLayout {
179    /// Creates a layout that uses equally sized and spaced tiles.
180    ///
181    /// All of the arguments must not be negative. The `cross_axis_count` argument must be greater
182    /// than zero.
183    pub fn new(
184        cross_axis_count: i32,
185        main_axis_stride: f64,
186        cross_axis_stride: f64,
187        child_main_axis_extent: f64,
188        child_cross_axis_extent: f64,
189        reverse_cross_axis: bool,
190    ) -> SliverGridRegularTileLayout {
191        debug_assert!(cross_axis_count > 0);
192        debug_assert!(main_axis_stride >= 0.0);
193        debug_assert!(cross_axis_stride >= 0.0);
194        debug_assert!(child_main_axis_extent >= 0.0);
195        debug_assert!(child_cross_axis_extent >= 0.0);
196        SliverGridRegularTileLayout {
197            cross_axis_count,
198            main_axis_stride,
199            cross_axis_stride,
200            child_main_axis_extent,
201            child_cross_axis_extent,
202            reverse_cross_axis,
203        }
204    }
205
206    fn get_offset_from_start_in_cross_axis(&self, cross_axis_start: f64) -> f64 {
207        if self.reverse_cross_axis {
208            return f64::from(self.cross_axis_count) * self.cross_axis_stride
209                - cross_axis_start
210                - self.child_cross_axis_extent
211                - (self.cross_axis_stride - self.child_cross_axis_extent);
212        }
213        cross_axis_start
214    }
215}
216
217impl SliverGridLayout for SliverGridRegularTileLayout {
218    fn get_min_child_index_for_scroll_offset(&self, scroll_offset: f64) -> i32 {
219        if self.main_axis_stride > PRECISION_ERROR_TOLERANCE {
220            self.cross_axis_count * (scroll_offset / self.main_axis_stride).trunc() as i32
221        } else {
222            0
223        }
224    }
225
226    fn get_max_child_index_for_scroll_offset(&self, scroll_offset: f64) -> i32 {
227        if self.main_axis_stride > 0.0 {
228            let main_axis_count = (scroll_offset / self.main_axis_stride).ceil() as i32;
229            return (self.cross_axis_count * main_axis_count - 1).max(0);
230        }
231        0
232    }
233
234    fn get_geometry_for_child_index(&self, index: i32) -> SliverGridGeometry {
235        let cross_axis_start = f64::from(index % self.cross_axis_count) * self.cross_axis_stride;
236        SliverGridGeometry::new(
237            f64::from(index / self.cross_axis_count) * self.main_axis_stride,
238            self.get_offset_from_start_in_cross_axis(cross_axis_start),
239            self.child_main_axis_extent,
240            self.child_cross_axis_extent,
241        )
242    }
243
244    fn compute_max_scroll_offset(&self, child_count: i32) -> f64 {
245        if child_count == 0 {
246            // There are no children in the grid. The max scroll offset should be zero.
247            return 0.0;
248        }
249        let main_axis_count = ((child_count - 1) / self.cross_axis_count) + 1;
250        let main_axis_spacing = self.main_axis_stride - self.child_main_axis_extent;
251        self.main_axis_stride * f64::from(main_axis_count) - main_axis_spacing
252    }
253}
254
255/// Controls the layout of tiles in a grid.
256///
257/// Given the current constraints on the grid, a [`SliverGridDelegate`] computes the layout for
258/// the tiles in the grid. The tiles can be placed arbitrarily, but it is more efficient to place
259/// tiles roughly in order by scroll offset because grids reify a contiguous sequence of children.
260///
261/// See also:
262///
263///  * [`SliverGridDelegateWithFixedCrossAxisCount`], which creates a layout with a fixed number
264///    of tiles in the cross axis.
265///  * [`SliverGridDelegateWithMaxCrossAxisExtent`], which creates a layout with tiles that have a
266///    maximum cross-axis extent.
267///  * `GridView`, which uses this delegate to control the layout of its tiles.
268///  * `SliverGrid`, which uses this delegate to control the layout of its tiles.
269///  * [`RenderSliverGrid`], which uses this delegate to control the layout of its tiles.
270pub trait SliverGridDelegate: Debug {
271    /// Returns information about the size and position of the tiles in the grid.
272    fn get_layout(&self, constraints: SliverConstraints) -> Box<dyn SliverGridLayout>;
273
274    /// Override this method to return true when the children need to be laid out.
275    ///
276    /// This should compare the fields of the current delegate and the given `old_delegate` and
277    /// return true if the fields are such that the layout would be different. The grid asks only
278    /// for an `old_delegate` of this delegate's own type.
279    fn should_relayout(&self, old_delegate: &dyn SliverGridDelegate) -> bool;
280
281    /// Downcast support for Dart's `runtimeType` comparison and `covariant` parameters.
282    fn as_any(&self) -> &dyn Any;
283}
284
285/// Creates grid layouts with a fixed number of tiles in the cross axis.
286///
287/// For example, if the grid is vertical, this delegate will create a layout with a fixed number
288/// of columns. If the grid is horizontal, this delegate will create a layout with a fixed number
289/// of rows.
290///
291/// This delegate creates grids with equally sized and spaced tiles.
292///
293/// Dart's named constructor arguments are the fluent setters.
294///
295/// See also:
296///
297///  * [`SliverGridDelegateWithMaxCrossAxisExtent`], which creates a layout with tiles that have a
298///    maximum cross-axis extent.
299///  * [`SliverGridDelegate`], which creates arbitrary layouts.
300///  * [`RenderSliverGrid`], which can use this delegate to control the layout of its tiles.
301#[derive(Clone, Debug)]
302pub struct SliverGridDelegateWithFixedCrossAxisCount {
303    /// The number of children in the cross axis.
304    pub cross_axis_count: i32,
305    /// The number of logical pixels between each child along the main axis.
306    pub main_axis_spacing: f64,
307    /// The number of logical pixels between each child along the cross axis.
308    pub cross_axis_spacing: f64,
309    /// The ratio of the cross-axis to the main-axis extent of each child.
310    pub child_aspect_ratio: f64,
311    /// The extent of each tile in the main axis. If provided it would define the logical pixels
312    /// taken by each tile in the main-axis.
313    ///
314    /// If `None`, [`child_aspect_ratio`](Self::child_aspect_ratio) is used instead.
315    pub main_axis_extent: Option<f64>,
316}
317
318impl SliverGridDelegateWithFixedCrossAxisCount {
319    /// Creates a delegate that makes grid layouts with a fixed number of tiles in the cross axis.
320    ///
321    /// The `cross_axis_count` must be greater than zero.
322    pub fn new(cross_axis_count: i32) -> SliverGridDelegateWithFixedCrossAxisCount {
323        debug_assert!(cross_axis_count > 0);
324        SliverGridDelegateWithFixedCrossAxisCount {
325            cross_axis_count,
326            main_axis_spacing: 0.0,
327            cross_axis_spacing: 0.0,
328            child_aspect_ratio: 1.0,
329            main_axis_extent: None,
330        }
331    }
332
333    /// Dart `SliverGridDelegateWithFixedCrossAxisCount(mainAxisSpacing:)`. Must not be negative.
334    pub fn main_axis_spacing(mut self, value: f64) -> Self {
335        debug_assert!(value >= 0.0);
336        self.main_axis_spacing = value;
337        self
338    }
339
340    /// Dart `SliverGridDelegateWithFixedCrossAxisCount(crossAxisSpacing:)`. Must not be negative.
341    pub fn cross_axis_spacing(mut self, value: f64) -> Self {
342        debug_assert!(value >= 0.0);
343        self.cross_axis_spacing = value;
344        self
345    }
346
347    /// Dart `SliverGridDelegateWithFixedCrossAxisCount(childAspectRatio:)`. Must be greater than
348    /// zero.
349    pub fn child_aspect_ratio(mut self, value: f64) -> Self {
350        debug_assert!(value > 0.0);
351        self.child_aspect_ratio = value;
352        self
353    }
354
355    /// Dart `SliverGridDelegateWithFixedCrossAxisCount(mainAxisExtent:)`. Must not be negative.
356    pub fn main_axis_extent(mut self, value: f64) -> Self {
357        debug_assert!(value >= 0.0);
358        self.main_axis_extent = Some(value);
359        self
360    }
361
362    fn debug_assert_is_valid(&self) -> bool {
363        debug_assert!(self.cross_axis_count > 0);
364        debug_assert!(self.main_axis_spacing >= 0.0);
365        debug_assert!(self.cross_axis_spacing >= 0.0);
366        debug_assert!(self.child_aspect_ratio > 0.0);
367        true
368    }
369}
370
371impl SliverGridDelegate for SliverGridDelegateWithFixedCrossAxisCount {
372    fn get_layout(&self, constraints: SliverConstraints) -> Box<dyn SliverGridLayout> {
373        debug_assert!(self.debug_assert_is_valid());
374        let usable_cross_axis_extent = (constraints.cross_axis_extent
375            - self.cross_axis_spacing * f64::from(self.cross_axis_count - 1))
376        .max(0.0);
377        let child_cross_axis_extent = usable_cross_axis_extent / f64::from(self.cross_axis_count);
378        let child_main_axis_extent = self
379            .main_axis_extent
380            .unwrap_or(child_cross_axis_extent / self.child_aspect_ratio);
381        Box::new(SliverGridRegularTileLayout::new(
382            self.cross_axis_count,
383            child_main_axis_extent + self.main_axis_spacing,
384            child_cross_axis_extent + self.cross_axis_spacing,
385            child_main_axis_extent,
386            child_cross_axis_extent,
387            axis_direction_is_reversed(constraints.cross_axis_direction),
388        ))
389    }
390
391    fn should_relayout(&self, old_delegate: &dyn SliverGridDelegate) -> bool {
392        let Some(old) = old_delegate.as_any().downcast_ref::<Self>() else {
393            return true;
394        };
395        old.cross_axis_count != self.cross_axis_count
396            || old.main_axis_spacing != self.main_axis_spacing
397            || old.cross_axis_spacing != self.cross_axis_spacing
398            || old.child_aspect_ratio != self.child_aspect_ratio
399            || old.main_axis_extent != self.main_axis_extent
400    }
401
402    fn as_any(&self) -> &dyn Any {
403        self
404    }
405}
406
407/// Creates grid layouts with tiles that each have a maximum cross-axis extent.
408///
409/// This delegate will select a cross-axis extent for the tiles that is as large as possible
410/// subject to the following conditions:
411///
412///  - The extent evenly divides the cross-axis extent of the grid.
413///  - The extent is at most [`max_cross_axis_extent`](Self::max_cross_axis_extent).
414///
415/// For example, if the grid is vertical, the grid is 500.0 pixels wide, and
416/// `max_cross_axis_extent` is 150.0, this delegate will create a grid with 4 columns that are
417/// 125.0 pixels wide.
418///
419/// This delegate creates grids with equally sized and spaced tiles.
420///
421/// Dart's named constructor arguments are the fluent setters.
422///
423/// See also:
424///
425///  * [`SliverGridDelegateWithFixedCrossAxisCount`], which creates a layout with a fixed number
426///    of tiles in the cross axis.
427///  * [`SliverGridDelegate`], which creates arbitrary layouts.
428///  * [`RenderSliverGrid`], which can use this delegate to control the layout of its tiles.
429#[derive(Clone, Debug)]
430pub struct SliverGridDelegateWithMaxCrossAxisExtent {
431    /// The maximum extent of tiles in the cross axis.
432    pub max_cross_axis_extent: f64,
433    /// The number of logical pixels between each child along the main axis.
434    pub main_axis_spacing: f64,
435    /// The number of logical pixels between each child along the cross axis.
436    pub cross_axis_spacing: f64,
437    /// The ratio of the cross-axis to the main-axis extent of each child.
438    pub child_aspect_ratio: f64,
439    /// The extent of each tile in the main axis. If provided it would define the logical pixels
440    /// taken by each tile in the main-axis.
441    ///
442    /// If `None`, [`child_aspect_ratio`](Self::child_aspect_ratio) is used instead.
443    pub main_axis_extent: Option<f64>,
444}
445
446impl SliverGridDelegateWithMaxCrossAxisExtent {
447    /// Creates a delegate that makes grid layouts with tiles that have a maximum cross-axis
448    /// extent.
449    ///
450    /// The `max_cross_axis_extent` must be greater than zero.
451    pub fn new(max_cross_axis_extent: f64) -> SliverGridDelegateWithMaxCrossAxisExtent {
452        debug_assert!(max_cross_axis_extent > 0.0);
453        SliverGridDelegateWithMaxCrossAxisExtent {
454            max_cross_axis_extent,
455            main_axis_spacing: 0.0,
456            cross_axis_spacing: 0.0,
457            child_aspect_ratio: 1.0,
458            main_axis_extent: None,
459        }
460    }
461
462    /// Dart `SliverGridDelegateWithMaxCrossAxisExtent(mainAxisSpacing:)`. Must not be negative.
463    pub fn main_axis_spacing(mut self, value: f64) -> Self {
464        debug_assert!(value >= 0.0);
465        self.main_axis_spacing = value;
466        self
467    }
468
469    /// Dart `SliverGridDelegateWithMaxCrossAxisExtent(crossAxisSpacing:)`. Must not be negative.
470    pub fn cross_axis_spacing(mut self, value: f64) -> Self {
471        debug_assert!(value >= 0.0);
472        self.cross_axis_spacing = value;
473        self
474    }
475
476    /// Dart `SliverGridDelegateWithMaxCrossAxisExtent(childAspectRatio:)`. Must be greater than
477    /// zero.
478    pub fn child_aspect_ratio(mut self, value: f64) -> Self {
479        debug_assert!(value > 0.0);
480        self.child_aspect_ratio = value;
481        self
482    }
483
484    /// Dart `SliverGridDelegateWithMaxCrossAxisExtent(mainAxisExtent:)`. Must not be negative.
485    pub fn main_axis_extent(mut self, value: f64) -> Self {
486        debug_assert!(value >= 0.0);
487        self.main_axis_extent = Some(value);
488        self
489    }
490
491    fn debug_assert_is_valid(&self, cross_axis_extent: f64) -> bool {
492        debug_assert!(cross_axis_extent > 0.0);
493        debug_assert!(self.max_cross_axis_extent > 0.0);
494        debug_assert!(self.main_axis_spacing >= 0.0);
495        debug_assert!(self.cross_axis_spacing >= 0.0);
496        debug_assert!(self.child_aspect_ratio > 0.0);
497        true
498    }
499}
500
501impl SliverGridDelegate for SliverGridDelegateWithMaxCrossAxisExtent {
502    fn get_layout(&self, constraints: SliverConstraints) -> Box<dyn SliverGridLayout> {
503        debug_assert!(self.debug_assert_is_valid(constraints.cross_axis_extent));
504        let cross_axis_count = (constraints.cross_axis_extent
505            / (self.max_cross_axis_extent + self.cross_axis_spacing))
506            .ceil() as i32;
507        // Ensure a minimum count of 1, can be zero and result in an infinite extent
508        // below when the window size is 0.
509        let cross_axis_count = cross_axis_count.max(1);
510        let usable_cross_axis_extent = (constraints.cross_axis_extent
511            - self.cross_axis_spacing * f64::from(cross_axis_count - 1))
512        .max(0.0);
513        let child_cross_axis_extent = usable_cross_axis_extent / f64::from(cross_axis_count);
514        let child_main_axis_extent = self
515            .main_axis_extent
516            .unwrap_or(child_cross_axis_extent / self.child_aspect_ratio);
517        Box::new(SliverGridRegularTileLayout::new(
518            cross_axis_count,
519            child_main_axis_extent + self.main_axis_spacing,
520            child_cross_axis_extent + self.cross_axis_spacing,
521            child_main_axis_extent,
522            child_cross_axis_extent,
523            axis_direction_is_reversed(constraints.cross_axis_direction),
524        ))
525    }
526
527    fn should_relayout(&self, old_delegate: &dyn SliverGridDelegate) -> bool {
528        let Some(old) = old_delegate.as_any().downcast_ref::<Self>() else {
529            return true;
530        };
531        old.max_cross_axis_extent != self.max_cross_axis_extent
532            || old.main_axis_spacing != self.main_axis_spacing
533            || old.cross_axis_spacing != self.cross_axis_spacing
534            || old.child_aspect_ratio != self.child_aspect_ratio
535            || old.main_axis_extent != self.main_axis_extent
536    }
537
538    fn as_any(&self) -> &dyn Any {
539        self
540    }
541}
542
543/// Parent data structure used by [`RenderSliverGrid`].
544#[derive(Debug, Default)]
545pub struct SliverGridParentData {
546    adaptor: SliverMultiBoxAdaptorParentData,
547    /// The offset of the child in the non-scrolling axis.
548    ///
549    /// If the scroll axis is vertical, this offset is from the left-most edge of the parent to
550    /// the left-most edge of the child. If the scroll axis is horizontal, this offset is from the
551    /// top-most edge of the parent to the top-most edge of the child.
552    pub cross_axis_offset: Option<f64>,
553}
554
555impl SliverGridParentData {
556    /// Creates parent data with no index, layout offset or cross-axis offset.
557    pub const fn new() -> SliverGridParentData {
558        SliverGridParentData {
559            adaptor: SliverMultiBoxAdaptorParentData::new(),
560            cross_axis_offset: None,
561        }
562    }
563}
564
565impl ParentData for SliverGridParentData {
566    fn detach(&mut self) {
567        ParentData::detach(&mut self.adaptor);
568    }
569
570    fn provide(&self, id: TypeId) -> Option<&dyn Any> {
571        if id == TypeId::of::<SliverGridParentData>() {
572            return Some(self);
573        }
574        self.adaptor.provide(id)
575    }
576
577    fn provide_mut(&mut self, id: TypeId) -> Option<&mut dyn Any> {
578        if id == TypeId::of::<SliverGridParentData>() {
579            return Some(self);
580        }
581        self.adaptor.provide_mut(id)
582    }
583}
584
585impl ContainerParentDataMixin for SliverGridParentData {
586    type ChildType = AnyRenderBox;
587
588    fn container_parent_data(&self) -> &ContainerParentData<AnyRenderBox> {
589        self.adaptor.container_parent_data()
590    }
591
592    fn container_parent_data_mut(&mut self) -> &mut ContainerParentData<AnyRenderBox> {
593        self.adaptor.container_parent_data_mut()
594    }
595}
596
597impl Display for SliverGridParentData {
598    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
599        write!(
600            f,
601            "crossAxisOffset={:?}; {}",
602            self.cross_axis_offset, self.adaptor
603        )
604    }
605}
606
607/// A sliver that places multiple box children in a two dimensional arrangement.
608///
609/// [`RenderSliverGrid`] places its children in arbitrary positions determined by
610/// [`grid_delegate`](Self::grid_delegate). Each child is forced to have the size specified by
611/// the `grid_delegate`.
612///
613/// See also:
614///
615///  * [`RenderSliverList`](crate::RenderSliverList), which places its children in a linear array.
616///  * [`RenderSliverFixedExtentList`](crate::RenderSliverFixedExtentList), which places its
617///    children in a linear array with a fixed extent in the main axis.
618pub struct RenderSliverGrid {
619    render_object: RenderObjectData,
620    render_sliver: RenderSliverData,
621    container: ContainerRenderObjectData<AnyRenderBox>,
622    adaptor: RenderSliverMultiBoxAdaptorData,
623    grid_delegate: Rc<dyn SliverGridDelegate>,
624}
625
626impl RenderSliverGrid {
627    /// Creates a sliver that contains multiple box children whose size and position are
628    /// determined by a delegate.
629    pub fn new(
630        app: &mut App,
631        child_manager: Rc<dyn RenderSliverBoxChildManager>,
632        grid_delegate: Rc<dyn SliverGridDelegate>,
633    ) -> RenderHandle<RenderSliverGrid> {
634        RenderHandle::new_sliver(
635            app,
636            RenderSliverGrid {
637                render_object: RenderObjectData::new(),
638                render_sliver: RenderSliverData::new(),
639                container: ContainerRenderObjectData::new(),
640                adaptor: RenderSliverMultiBoxAdaptorData::new(child_manager),
641                grid_delegate,
642            },
643        )
644    }
645
646    /// The delegate that controls the size and position of the children.
647    pub fn grid_delegate(self: RenderHandle<Self>, app: &App) -> Rc<dyn SliverGridDelegate> {
648        Rc::clone(&self.get(app).grid_delegate)
649    }
650
651    /// Sets [`grid_delegate`](Self::grid_delegate).
652    pub fn set_grid_delegate(
653        self: RenderHandle<Self>,
654        app: &mut App,
655        value: Rc<dyn SliverGridDelegate>,
656    ) {
657        let old = self.grid_delegate(app);
658        if Rc::ptr_eq(&old, &value) {
659            return;
660        }
661        if value.as_any().type_id() != old.as_any().type_id() || value.should_relayout(&*old) {
662            self.mark_needs_layout(app);
663        }
664        self.get_mut(app).grid_delegate = value;
665    }
666
667    /// Stores where `child` was laid out, from `geometry`.
668    fn place(app: &mut App, child: AnyRenderBox, geometry: SliverGridGeometry) {
669        let child_parent_data = child
670            .as_object()
671            .parent_data_of_mut::<SliverGridParentData>(app);
672        child_parent_data
673            .adaptor
674            .set_layout_offset(Some(geometry.scroll_offset));
675        child_parent_data.cross_axis_offset = Some(geometry.cross_axis_offset);
676    }
677}
678
679impl ContainerRenderObjectMixin for RenderSliverGrid {
680    type ChildType = AnyRenderBox;
681    type ParentDataType = SliverMultiBoxAdaptorParentData;
682
683    fn container_data(
684        self: RenderHandle<Self>,
685        app: &App,
686    ) -> &ContainerRenderObjectData<AnyRenderBox> {
687        &self.get(app).container
688    }
689
690    fn container_data_mut(
691        self: RenderHandle<Self>,
692        app: &mut App,
693    ) -> &mut ContainerRenderObjectData<AnyRenderBox> {
694        &mut self.get_mut(app).container
695    }
696
697    fn insert(
698        self: RenderHandle<Self>,
699        app: &mut App,
700        child: AnyRenderBox,
701        after: Option<AnyRenderBox>,
702    ) {
703        RenderSliverMultiBoxAdaptor::insert(self, app, child, after)
704    }
705
706    fn move_child(
707        self: RenderHandle<Self>,
708        app: &mut App,
709        child: AnyRenderBox,
710        after: Option<AnyRenderBox>,
711    ) {
712        RenderSliverMultiBoxAdaptor::move_child(self, app, child, after)
713    }
714
715    fn remove(self: RenderHandle<Self>, app: &mut App, child: AnyRenderBox) {
716        RenderSliverMultiBoxAdaptor::remove(self, app, child)
717    }
718
719    fn remove_all(self: RenderHandle<Self>, app: &mut App) {
720        RenderSliverMultiBoxAdaptor::remove_all(self, app)
721    }
722}
723
724impl RenderSliverHelpers for RenderSliverGrid {}
725
726impl RenderSliverWithKeepAliveMixin for RenderSliverGrid {}
727
728impl RenderSliverMultiBoxAdaptor for RenderSliverGrid {
729    crate::render_sliver_multi_box_adaptor_accessors!();
730}
731
732impl RenderObject for RenderSliverGrid {
733    crate::render_object_accessors!();
734
735    fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
736        if !child.parent_data_is::<SliverGridParentData>(app) {
737            child.set_parent_data(app, SliverGridParentData::new());
738        }
739    }
740
741    fn visit_children(
742        self: RenderHandle<Self>,
743        app: &App,
744        visitor: &mut dyn FnMut(AnyRenderObject),
745    ) {
746        RenderSliverMultiBoxAdaptor::visit_children(self, app, visitor)
747    }
748
749    fn did_attach(self: RenderHandle<Self>, app: &mut App, owner: Handle<PipelineOwner>) {
750        RenderSliverMultiBoxAdaptor::did_attach(self, app, owner)
751    }
752
753    fn did_detach(self: RenderHandle<Self>, app: &mut App) {
754        RenderSliverMultiBoxAdaptor::did_detach(self, app)
755    }
756
757    fn redepth_children(self: RenderHandle<Self>, app: &mut App) {
758        RenderSliverMultiBoxAdaptor::redepth_children(self, app)
759    }
760
761    fn apply_paint_transform(
762        self: RenderHandle<Self>,
763        app: &App,
764        child: AnyRenderObject,
765        transform: &mut Matrix4,
766    ) {
767        RenderSliverMultiBoxAdaptor::apply_paint_transform(self, app, child, transform)
768    }
769
770    fn paint(
771        self: RenderHandle<Self>,
772        app: &mut App,
773        context: &mut PaintingContext,
774        offset: Offset,
775    ) {
776        RenderSliverMultiBoxAdaptor::paint(self, app, context, offset)
777    }
778
779    fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
780        let constraints = self.constraints(app);
781        let child_manager = self.child_manager(app);
782        child_manager.did_start_layout(app);
783        child_manager.set_did_underflow(app, false);
784
785        let scroll_offset = constraints.scroll_offset + constraints.cache_origin;
786        debug_assert!(scroll_offset >= 0.0);
787        let remaining_extent = constraints.remaining_cache_extent;
788        debug_assert!(remaining_extent >= 0.0);
789        let target_end_scroll_offset = scroll_offset + remaining_extent;
790
791        let layout = self.grid_delegate(app).get_layout(constraints);
792
793        let first_index = layout.get_min_child_index_for_scroll_offset(scroll_offset);
794        let target_last_index = target_end_scroll_offset
795            .is_finite()
796            .then(|| layout.get_max_child_index_for_scroll_offset(target_end_scroll_offset));
797
798        if self.first_child(app).is_some() {
799            let leading_garbage = self.calculate_leading_garbage(app, first_index);
800            let trailing_garbage = match target_last_index {
801                Some(last_index) => self.calculate_trailing_garbage(app, last_index),
802                None => 0,
803            };
804            self.collect_garbage(app, leading_garbage, trailing_garbage);
805        } else {
806            self.collect_garbage(app, 0, 0);
807        }
808
809        let first_child_grid_geometry = layout.get_geometry_for_child_index(first_index);
810
811        if self.first_child(app).is_none()
812            && !self.add_initial_child(app, first_index, first_child_grid_geometry.scroll_offset)
813        {
814            // There are either no children, or we are past the end of all our children.
815            let max = layout.compute_max_scroll_offset(child_manager.child_count(app));
816            self.set_geometry(
817                app,
818                SliverGeometry::builder()
819                    .scroll_extent(max)
820                    .max_paint_extent(max)
821                    .build(),
822            );
823            child_manager.did_finish_layout(app);
824            return;
825        }
826
827        let leading_scroll_offset = first_child_grid_geometry.scroll_offset;
828        let mut trailing_scroll_offset = first_child_grid_geometry.trailing_scroll_offset();
829        let mut trailing_child_with_layout: Option<AnyRenderBox> = None;
830        let mut reached_end = false;
831
832        let first_child = self.first_child(app).expect("added above");
833        let mut index = self.index_of(app, first_child) - 1;
834        while index >= first_index {
835            let grid_geometry = layout.get_geometry_for_child_index(index);
836            let child = self
837                .insert_and_layout_leading_child(
838                    app,
839                    grid_geometry.get_box_constraints(constraints),
840                    false,
841                )
842                .expect("the children before the first one are there");
843            Self::place(app, child, grid_geometry);
844            debug_assert_eq!(
845                child
846                    .as_object()
847                    .parent_data_of::<SliverMultiBoxAdaptorParentData>(app)
848                    .index,
849                Some(index)
850            );
851            trailing_child_with_layout.get_or_insert(child);
852            trailing_scroll_offset =
853                trailing_scroll_offset.max(grid_geometry.trailing_scroll_offset());
854            index -= 1;
855        }
856
857        if trailing_child_with_layout.is_none() {
858            let first_child = self.first_child(app).expect("the child list is not empty");
859            first_child.layout(
860                app,
861                first_child_grid_geometry.get_box_constraints(constraints),
862                false,
863            );
864            Self::place(app, first_child, first_child_grid_geometry);
865            trailing_child_with_layout = Some(first_child);
866        }
867
868        let mut index = self.index_of(app, trailing_child_with_layout.expect("set above")) + 1;
869        while target_last_index.is_none_or(|last_index| index <= last_index) {
870            let grid_geometry = layout.get_geometry_for_child_index(index);
871            let child_constraints = grid_geometry.get_box_constraints(constraints);
872            let after = trailing_child_with_layout.expect("set above");
873            let mut child = self.child_after(app, after);
874            if child.is_none_or(|current| self.index_of(app, current) != index) {
875                child = self.insert_and_layout_child(app, child_constraints, Some(after), false);
876                if child.is_none() {
877                    reached_end = true;
878                    // We have run out of children.
879                    break;
880                }
881            } else {
882                child
883                    .expect("checked")
884                    .layout(app, child_constraints, false);
885            }
886            let child = child.expect("set above");
887            trailing_child_with_layout = Some(child);
888            Self::place(app, child, grid_geometry);
889            debug_assert_eq!(
890                child
891                    .as_object()
892                    .parent_data_of::<SliverMultiBoxAdaptorParentData>(app)
893                    .index,
894                Some(index)
895            );
896            trailing_scroll_offset =
897                trailing_scroll_offset.max(grid_geometry.trailing_scroll_offset());
898            index += 1;
899        }
900
901        let last_index = self.index_of(app, self.last_child(app).expect("not empty"));
902
903        debug_assert!(self.debug_assert_child_list_is_non_empty_and_contiguous(app));
904        debug_assert_eq!(
905            self.index_of(app, self.first_child(app).expect("not empty")),
906            first_index
907        );
908        debug_assert!(target_last_index.is_none_or(|target| last_index <= target));
909
910        let estimated_total_extent = if reached_end {
911            trailing_scroll_offset
912        } else {
913            child_manager.estimate_max_scroll_offset(
914                app,
915                constraints,
916                Some(first_index),
917                Some(last_index),
918                Some(leading_scroll_offset),
919                Some(trailing_scroll_offset),
920            )
921        };
922        let paint_extent = calculate_paint_offset(
923            constraints,
924            constraints.scroll_offset.min(leading_scroll_offset),
925            trailing_scroll_offset,
926        );
927        let cache_extent =
928            calculate_cache_offset(constraints, leading_scroll_offset, trailing_scroll_offset);
929
930        self.set_geometry(
931            app,
932            SliverGeometry::builder()
933                .scroll_extent(estimated_total_extent)
934                .paint_extent(paint_extent)
935                .max_paint_extent(estimated_total_extent)
936                .cache_extent(cache_extent)
937                .has_visual_overflow(
938                    estimated_total_extent > paint_extent
939                        || constraints.scroll_offset > 0.0
940                        || constraints.overlap != 0.0,
941                )
942                .build(),
943        );
944
945        // We may have started the layout while scrolled to the end, which
946        // would not expose a new child.
947        if estimated_total_extent == trailing_scroll_offset {
948            child_manager.set_did_underflow(app, true);
949        }
950        child_manager.did_finish_layout(app);
951    }
952}
953
954impl RenderSliver for RenderSliverGrid {
955    crate::render_sliver_accessors!();
956
957    fn hit_test_children(
958        self: RenderHandle<Self>,
959        app: &mut App,
960        result: &mut SliverHitTestResult<'_>,
961        main_axis_position: f64,
962        cross_axis_position: f64,
963    ) -> bool {
964        RenderSliverMultiBoxAdaptor::hit_test_children(
965            self,
966            app,
967            result,
968            main_axis_position,
969            cross_axis_position,
970        )
971    }
972
973    fn child_main_axis_position(
974        self: RenderHandle<Self>,
975        app: &App,
976        child: AnyRenderObject,
977    ) -> f64 {
978        RenderSliverMultiBoxAdaptor::child_main_axis_position(self, app, child)
979    }
980
981    fn child_cross_axis_position(
982        self: RenderHandle<Self>,
983        app: &App,
984        child: AnyRenderObject,
985    ) -> f64 {
986        child
987            .parent_data_of::<SliverGridParentData>(app)
988            .cross_axis_offset
989            .expect("laid out")
990    }
991
992    fn child_scroll_offset(
993        self: RenderHandle<Self>,
994        app: &App,
995        child: AnyRenderObject,
996    ) -> Option<f64> {
997        RenderSliverMultiBoxAdaptor::child_scroll_offset(self, app, child)
998    }
999}
1000
1001#[cfg(test)]
1002mod tests {
1003    use inset_embedder::Size;
1004    use inset_foundation::AppCell;
1005    use inset_gestures::HitTestResult;
1006    use inset_painting::AxisDirection;
1007
1008    use super::*;
1009    use crate::sliver::GrowthDirection;
1010    use crate::sliver_multi_box_adaptor::test_support::TestChildManager;
1011    use crate::sliver_multi_box_adaptor::viewport_test_support::ScrollHarness;
1012    use crate::viewport_offset::ScrollDirection;
1013
1014    const VIEWPORT: Size = Size::new(500.0, 200.0);
1015
1016    fn sliver_grid(
1017        app: &mut App,
1018        count: usize,
1019        delegate: Rc<dyn SliverGridDelegate>,
1020    ) -> (
1021        RenderHandle<RenderSliverGrid>,
1022        Rc<TestChildManager<RenderSliverGrid>>,
1023        ScrollHarness,
1024    ) {
1025        let manager = TestChildManager::new(vec![1000.0; count]);
1026        let grid = RenderSliverGrid::new(app, manager.clone(), delegate);
1027        manager.attach(grid);
1028        let harness = ScrollHarness::new(app, grid.as_sliver(), VIEWPORT);
1029        (grid, manager, harness)
1030    }
1031
1032    /// Vertical constraints `cross_axis_extent` wide, at the top of a 600 high viewport.
1033    fn constraints(cross_axis_extent: f64) -> SliverConstraints {
1034        SliverConstraints::new(
1035            AxisDirection::Down,
1036            GrowthDirection::Forward,
1037            ScrollDirection::Idle,
1038            0.0,
1039            0.0,
1040            0.0,
1041            600.0,
1042            cross_axis_extent,
1043            AxisDirection::Right,
1044            600.0,
1045            1100.0,
1046            0.0,
1047        )
1048    }
1049
1050    /// Where each child is laid out: its index, scroll offset, cross-axis offset and size.
1051    fn placed(app: &App, grid: RenderHandle<RenderSliverGrid>) -> Vec<(i32, f64, f64, Size)> {
1052        let mut placed = Vec::new();
1053        let mut child = grid.first_child(app);
1054        while let Some(current) = child {
1055            let data = current
1056                .as_object()
1057                .parent_data_of::<SliverGridParentData>(app);
1058            placed.push((
1059                data.adaptor.index.expect("indexed"),
1060                data.adaptor.layout_offset().expect("laid out"),
1061                data.cross_axis_offset.expect("laid out"),
1062                current.size(app),
1063            ));
1064            child = grid.child_after(app, current);
1065        }
1066        placed
1067    }
1068
1069    /// `sliver_grid_test.dart`: the max-extent delegate fits as many columns as it can, each as
1070    /// wide as an even split allows.
1071    #[test]
1072    fn a_max_extent_grid_fits_as_many_columns_as_it_can() {
1073        let delegate = SliverGridDelegateWithMaxCrossAxisExtent::new(150.0);
1074        let layout = delegate.get_layout(constraints(500.0));
1075        let geometry = layout.get_geometry_for_child_index(5);
1076        assert_eq!(geometry.cross_axis_extent, 125.0, "4 columns of 125 in 500");
1077        assert_eq!(geometry.cross_axis_offset, 125.0);
1078        assert_eq!(geometry.scroll_offset, 125.0, "the second row");
1079        assert_eq!(layout.compute_max_scroll_offset(9), 375.0, "3 rows");
1080        assert_eq!(layout.compute_max_scroll_offset(0), 0.0);
1081    }
1082
1083    /// A grid whose cross axis runs right to left places its first column on the right.
1084    #[test]
1085    fn a_reversed_cross_axis_places_the_first_column_last() {
1086        let mut leftward = constraints(400.0);
1087        leftward.cross_axis_direction = AxisDirection::Left;
1088        let layout = SliverGridDelegateWithFixedCrossAxisCount::new(4)
1089            .cross_axis_spacing(20.0)
1090            .get_layout(leftward);
1091        // Columns are 85 wide with 20 between them: the first sits at 400 - 85.
1092        assert_eq!(
1093            layout.get_geometry_for_child_index(0).cross_axis_offset,
1094            315.0
1095        );
1096        assert_eq!(
1097            layout.get_geometry_for_child_index(3).cross_axis_offset,
1098            0.0
1099        );
1100    }
1101
1102    #[test]
1103    fn a_grid_lays_out_only_the_rows_it_shows_with_their_spacing() {
1104        let cell = AppCell::new();
1105        let mut app = cell.borrow_mut();
1106        let delegate = SliverGridDelegateWithFixedCrossAxisCount::new(2)
1107            .main_axis_spacing(10.0)
1108            .cross_axis_spacing(20.0)
1109            .child_aspect_ratio(2.0);
1110        let (grid, manager, _harness) = sliver_grid(&mut app, 100, Rc::new(delegate));
1111        // Each tile is (500 - 20) / 2 = 240 wide and 120 tall, so rows are 130 apart: the 200
1112        // high viewport and its 250 of cache reach into the fourth row.
1113        let placed = placed(&app, grid);
1114        assert_eq!(
1115            placed[..3],
1116            [
1117                (0, 0.0, 0.0, Size::new(240.0, 120.0)),
1118                (1, 0.0, 260.0, Size::new(240.0, 120.0)),
1119                (2, 130.0, 0.0, Size::new(240.0, 120.0)),
1120            ]
1121        );
1122        assert_eq!(placed.len(), 8, "four rows of two");
1123        assert!(manager.created.borrow().iter().all(|index| *index < 8));
1124    }
1125
1126    #[test]
1127    fn scrolling_a_grid_replaces_its_rows() {
1128        let cell = AppCell::new();
1129        let mut app = cell.borrow_mut();
1130        let delegate = SliverGridDelegateWithFixedCrossAxisCount::new(4).main_axis_extent(100.0);
1131        let (grid, _, harness) = sliver_grid(&mut app, 100, Rc::new(delegate));
1132        harness.scroll_to(&mut app, 1000.0);
1133        let placed = placed(&app, grid);
1134        // The cache reaches 250 before the scroll offset, to 750: row 7, which starts at 700.
1135        assert_eq!((placed[0].0, placed[0].1), (28, 700.0));
1136        assert!(placed.iter().all(|(index, ..)| *index >= 28));
1137        assert!(grid.geometry(&app).has_visual_overflow);
1138    }
1139
1140    #[test]
1141    fn a_short_grid_reports_its_exact_extent() {
1142        let cell = AppCell::new();
1143        let mut app = cell.borrow_mut();
1144        let delegate = SliverGridDelegateWithFixedCrossAxisCount::new(4).main_axis_extent(50.0);
1145        let (grid, manager, _harness) = sliver_grid(&mut app, 6, Rc::new(delegate));
1146        let geometry = grid.geometry(&app);
1147        assert_eq!(geometry.scroll_extent, 100.0, "two rows of 50");
1148        assert_eq!(geometry.paint_extent, 100.0);
1149        assert!(!geometry.has_visual_overflow);
1150        assert!(manager.did_underflow.get());
1151    }
1152
1153    /// A hit lands on the tile under it, which only its cross-axis offset tells apart from the
1154    /// others in its row.
1155    #[test]
1156    fn a_grid_hit_tests_the_tile_under_the_point() {
1157        let cell = AppCell::new();
1158        let mut app = cell.borrow_mut();
1159        let delegate = SliverGridDelegateWithFixedCrossAxisCount::new(5).main_axis_extent(100.0);
1160        let (grid, _, _harness) = sliver_grid(&mut app, 20, Rc::new(delegate));
1161        let tile = placed(&app, grid)
1162            .iter()
1163            .position(|(index, ..)| *index == 8)
1164            .expect("tile 8 is laid out");
1165        let mut child = grid.first_child(&app).expect("tiles");
1166        for _ in 0..tile {
1167            child = grid.child_after(&app, child).expect("tile");
1168        }
1169        assert_eq!(
1170            RenderSliver::child_cross_axis_position(grid, &app, child.as_object()),
1171            300.0
1172        );
1173
1174        let mut result = HitTestResult::new();
1175        let hit = grid.hit_test(
1176            &mut app,
1177            &mut SliverHitTestResult::wrap(&mut result),
1178            150.0,
1179            350.0,
1180        );
1181        assert!(hit);
1182        assert_eq!(result.path().len(), 2, "tile 8 and the grid");
1183    }
1184
1185    #[test]
1186    fn a_new_delegate_relays_out_only_when_it_changes_the_tiles() {
1187        let cell = AppCell::new();
1188        let mut app = cell.borrow_mut();
1189        let delegate = SliverGridDelegateWithFixedCrossAxisCount::new(2);
1190        let (grid, _, harness) = sliver_grid(&mut app, 10, Rc::new(delegate.clone()));
1191        grid.set_grid_delegate(&mut app, Rc::new(delegate.clone()));
1192        assert!(!grid.as_object().debug_needs_layout(&app), "the same tiles");
1193        grid.set_grid_delegate(&mut app, Rc::new(delegate.main_axis_spacing(4.0)));
1194        assert!(grid.as_object().debug_needs_layout(&app));
1195        harness.owner.flush_layout(&mut app);
1196        grid.set_grid_delegate(
1197            &mut app,
1198            Rc::new(SliverGridDelegateWithMaxCrossAxisExtent::new(250.0)),
1199        );
1200        assert!(
1201            grid.as_object().debug_needs_layout(&app),
1202            "another kind of delegate"
1203        );
1204    }
1205}