Skip to main content

inset_rendering/
sliver_padding.rs

1//! Flutter counterpart: `rendering/sliver_padding.dart`.
2//!
3//! Debug paint overlays wait; see `PORTING.md`.
4
5use inset_embedder::{Matrix4, Offset, TextDirection};
6use inset_foundation::App;
7use inset_painting::{Axis, AxisDirection, EdgeInsets, EdgeInsetsGeometry};
8
9use crate::object::{
10    AnyRenderObject, RenderHandle, RenderObject, RenderObjectData, RenderObjectWithChildData,
11    RenderObjectWithChildMixin,
12};
13use crate::painting_context::PaintingContext;
14use crate::sliver::{
15    AnyRenderSliver, RenderSliver, RenderSliverData, SliverGeometry, SliverHitTestResult,
16    SliverPhysicalParentData, apply_growth_direction_to_axis_direction, calculate_cache_offset,
17    calculate_paint_offset,
18};
19
20/// A sliver that insets another sliver by applying resolved padding on each side.
21///
22/// Flutter's `RenderSliverEdgeInsetsPadding`: the shared bodies of a padding sliver, over the
23/// [`resolved_padding`](Self::resolved_padding) the implementor supplies.
24pub trait RenderSliverEdgeInsetsPadding:
25    RenderSliver + RenderObjectWithChildMixin<ChildType = AnyRenderSliver>
26{
27    /// The amount to pad the child in each dimension.
28    ///
29    /// The offsets are specified in terms of visual edges, left, top, right, and bottom. These
30    /// values are not affected by the [`TextDirection`].
31    ///
32    /// Must be `Some` and contain no negative values when `perform_layout` is called.
33    fn resolved_padding(self: RenderHandle<Self>, app: &App) -> Option<EdgeInsets>;
34
35    /// The padding in the scroll direction on the side nearest the 0.0 scroll direction.
36    ///
37    /// Only valid after layout has started, since before layout the render object doesn't know
38    /// what direction it will be laid out in.
39    fn before_padding(self: RenderHandle<Self>, app: &App) -> f64 {
40        let padding = self
41            .resolved_padding(app)
42            .expect("the padding is resolved before layout");
43        let constraints = self.constraints(app);
44        match apply_growth_direction_to_axis_direction(
45            constraints.axis_direction,
46            constraints.growth_direction,
47        ) {
48            AxisDirection::Up => padding.bottom,
49            AxisDirection::Right => padding.left,
50            AxisDirection::Down => padding.top,
51            AxisDirection::Left => padding.right,
52        }
53    }
54
55    /// The padding in the scroll direction on the side furthest from the 0.0 scroll offset.
56    ///
57    /// Only valid after layout has started.
58    fn after_padding(self: RenderHandle<Self>, app: &App) -> f64 {
59        let padding = self
60            .resolved_padding(app)
61            .expect("the padding is resolved before layout");
62        let constraints = self.constraints(app);
63        match apply_growth_direction_to_axis_direction(
64            constraints.axis_direction,
65            constraints.growth_direction,
66        ) {
67            AxisDirection::Up => padding.top,
68            AxisDirection::Right => padding.right,
69            AxisDirection::Down => padding.bottom,
70            AxisDirection::Left => padding.left,
71        }
72    }
73
74    /// The total padding in the [`crate::SliverConstraints::axis_direction`]. (In other words,
75    /// for a vertical downwards-growing list, the sum of the padding on the top and bottom.)
76    ///
77    /// Only valid after layout has started.
78    fn main_axis_padding(self: RenderHandle<Self>, app: &App) -> f64 {
79        let padding = self
80            .resolved_padding(app)
81            .expect("the padding is resolved before layout");
82        // Dart's `EdgeInsetsGeometry.along(axis)`, which `EdgeInsets` inherits.
83        match self.constraints(app).axis() {
84            Axis::Horizontal => padding.horizontal(),
85            Axis::Vertical => padding.vertical(),
86        }
87    }
88
89    /// The total padding in the cross-axis direction. (In other words, for a vertical
90    /// downwards-growing list, the sum of the padding on the left and right.)
91    ///
92    /// Only valid after layout has started.
93    fn cross_axis_padding(self: RenderHandle<Self>, app: &App) -> f64 {
94        let padding = self
95            .resolved_padding(app)
96            .expect("the padding is resolved before layout");
97        match self.constraints(app).axis() {
98            Axis::Horizontal => padding.vertical(),
99            Axis::Vertical => padding.horizontal(),
100        }
101    }
102
103    /// The body of Flutter's `setupParentData` override.
104    fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
105        let _ = self;
106        if !child.parent_data_is::<SliverPhysicalParentData>(app) {
107            child.set_parent_data(app, SliverPhysicalParentData::new());
108        }
109    }
110
111    /// The body of Flutter's `performLayout` override.
112    fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
113        let constraints = self.constraints(app);
114        let paint_offset = |from: f64, to: f64| calculate_paint_offset(constraints, from, to);
115        let cache_offset = |from: f64, to: f64| calculate_cache_offset(constraints, from, to);
116
117        let resolved_padding = self
118            .resolved_padding(app)
119            .expect("the padding is resolved before layout");
120        let before_padding = self.before_padding(app);
121        let after_padding = self.after_padding(app);
122        let main_axis_padding = self.main_axis_padding(app);
123        let cross_axis_padding = self.cross_axis_padding(app);
124        let Some(child) = self.child(app) else {
125            let paint_extent = paint_offset(0.0, main_axis_padding);
126            let cache_extent = cache_offset(0.0, main_axis_padding);
127            self.set_geometry(
128                app,
129                SliverGeometry::builder()
130                    .scroll_extent(main_axis_padding)
131                    .paint_extent(paint_extent.min(constraints.remaining_paint_extent))
132                    .max_paint_extent(main_axis_padding)
133                    .cache_extent(cache_extent)
134                    .build(),
135            );
136            return;
137        };
138        let before_padding_paint_extent = paint_offset(0.0, before_padding);
139        let mut overlap = constraints.overlap;
140        if overlap > 0.0 {
141            overlap = (constraints.overlap - before_padding_paint_extent).max(0.0);
142        }
143        let child_constraints = constraints
144            .copy_with()
145            .scroll_offset((constraints.scroll_offset - before_padding).max(0.0))
146            .cache_origin((constraints.cache_origin + before_padding).min(0.0))
147            .overlap(overlap)
148            .remaining_paint_extent(
149                constraints.remaining_paint_extent - paint_offset(0.0, before_padding),
150            )
151            .remaining_cache_extent(
152                constraints.remaining_cache_extent - cache_offset(0.0, before_padding),
153            )
154            .cross_axis_extent((constraints.cross_axis_extent - cross_axis_padding).max(0.0))
155            .preceding_scroll_extent(before_padding + constraints.preceding_scroll_extent);
156        child.layout(app, child_constraints, true);
157
158        let child_layout_geometry = child.geometry(app);
159        if let Some(correction) = child_layout_geometry.scroll_offset_correction {
160            self.set_geometry(
161                app,
162                SliverGeometry::builder()
163                    .scroll_offset_correction(correction)
164                    .build(),
165            );
166            return;
167        }
168        let scroll_extent = child_layout_geometry.scroll_extent;
169        let before_padding_cache_extent = cache_offset(0.0, before_padding);
170        let after_padding_cache_extent = cache_offset(
171            before_padding + scroll_extent,
172            main_axis_padding + scroll_extent,
173        );
174        let after_padding_paint_extent = paint_offset(
175            before_padding + scroll_extent,
176            main_axis_padding + scroll_extent,
177        );
178        let main_axis_padding_cache_extent =
179            before_padding_cache_extent + after_padding_cache_extent;
180        let main_axis_padding_paint_extent =
181            before_padding_paint_extent + after_padding_paint_extent;
182        let paint_extent = (before_padding_paint_extent
183            + child_layout_geometry
184                .paint_extent
185                .max(child_layout_geometry.layout_extent + after_padding_paint_extent))
186        .min(constraints.remaining_paint_extent);
187        self.set_geometry(
188            app,
189            SliverGeometry::builder()
190                .paint_origin(child_layout_geometry.paint_origin)
191                .scroll_extent(main_axis_padding + scroll_extent)
192                .paint_extent(paint_extent)
193                .layout_extent(
194                    (main_axis_padding_paint_extent + child_layout_geometry.layout_extent)
195                        .min(paint_extent),
196                )
197                .cache_extent(
198                    (main_axis_padding_cache_extent + child_layout_geometry.cache_extent)
199                        .min(constraints.remaining_cache_extent),
200                )
201                .max_paint_extent(main_axis_padding + child_layout_geometry.max_paint_extent)
202                .hit_test_extent(
203                    (main_axis_padding_paint_extent + child_layout_geometry.paint_extent)
204                        .max(before_padding_paint_extent + child_layout_geometry.hit_test_extent),
205                )
206                .has_visual_overflow(child_layout_geometry.has_visual_overflow)
207                .build(),
208        );
209        let calculated_offset = match apply_growth_direction_to_axis_direction(
210            constraints.axis_direction,
211            constraints.growth_direction,
212        ) {
213            AxisDirection::Up => paint_offset(
214                resolved_padding.bottom + scroll_extent,
215                resolved_padding.vertical() + scroll_extent,
216            ),
217            AxisDirection::Left => paint_offset(
218                resolved_padding.right + scroll_extent,
219                resolved_padding.horizontal() + scroll_extent,
220            ),
221            AxisDirection::Right => paint_offset(0.0, resolved_padding.left),
222            AxisDirection::Down => paint_offset(0.0, resolved_padding.top),
223        };
224        let child_paint_offset = match constraints.axis() {
225            Axis::Horizontal => Offset::new(calculated_offset, resolved_padding.top),
226            Axis::Vertical => Offset::new(resolved_padding.left, calculated_offset),
227        };
228        child
229            .as_object()
230            .parent_data_of_mut::<SliverPhysicalParentData>(app)
231            .paint_offset = child_paint_offset;
232        debug_assert_eq!(before_padding, self.before_padding(app));
233        debug_assert_eq!(after_padding, self.after_padding(app));
234        debug_assert_eq!(main_axis_padding, self.main_axis_padding(app));
235        debug_assert_eq!(cross_axis_padding, self.cross_axis_padding(app));
236    }
237
238    /// The body of Flutter's `hitTestChildren` override.
239    fn hit_test_children(
240        self: RenderHandle<Self>,
241        app: &mut App,
242        result: &mut SliverHitTestResult<'_>,
243        main_axis_position: f64,
244        cross_axis_position: f64,
245    ) -> bool {
246        let Some(child) = self.child(app) else {
247            return false;
248        };
249        if child.geometry(app).hit_test_extent <= 0.0 {
250            return false;
251        }
252        let paint_offset = child
253            .as_object()
254            .parent_data_of::<SliverPhysicalParentData>(app)
255            .paint_offset;
256        let main_axis_offset =
257            RenderSliverEdgeInsetsPadding::child_main_axis_position(self, app, child.as_object());
258        let cross_axis_offset =
259            RenderSliverEdgeInsetsPadding::child_cross_axis_position(self, app, child.as_object());
260        result.add_with_axis_offset(
261            Some(paint_offset),
262            main_axis_offset,
263            cross_axis_offset,
264            main_axis_position,
265            cross_axis_position,
266            |result, main, cross| child.hit_test(app, result, main, cross),
267        )
268    }
269
270    /// The body of Flutter's `childMainAxisPosition` override.
271    fn child_main_axis_position(
272        self: RenderHandle<Self>,
273        app: &App,
274        child: AnyRenderObject,
275    ) -> f64 {
276        debug_assert_eq!(Some(child), self.child(app).map(AnyRenderSliver::as_object));
277        let constraints = self.constraints(app);
278        let before_padding = self.before_padding(app);
279        self.calculate_paint_offset(app, constraints, 0.0, before_padding)
280    }
281
282    /// The body of Flutter's `childCrossAxisPosition` override.
283    fn child_cross_axis_position(
284        self: RenderHandle<Self>,
285        app: &App,
286        child: AnyRenderObject,
287    ) -> f64 {
288        debug_assert_eq!(Some(child), self.child(app).map(AnyRenderSliver::as_object));
289        let padding = self
290            .resolved_padding(app)
291            .expect("the padding is resolved before layout");
292        match self.constraints(app).axis() {
293            Axis::Horizontal => padding.top,
294            Axis::Vertical => padding.left,
295        }
296    }
297
298    /// The body of Flutter's `childScrollOffset` override.
299    fn child_scroll_offset(
300        self: RenderHandle<Self>,
301        app: &App,
302        child: AnyRenderObject,
303    ) -> Option<f64> {
304        debug_assert_eq!(child.parent(app), Some(self.as_object()));
305        Some(self.before_padding(app))
306    }
307
308    /// The body of Flutter's `applyPaintTransform` override.
309    fn apply_paint_transform(
310        self: RenderHandle<Self>,
311        app: &App,
312        child: AnyRenderObject,
313        transform: &mut Matrix4,
314    ) {
315        debug_assert_eq!(Some(child), self.child(app).map(AnyRenderSliver::as_object));
316        child
317            .parent_data_of::<SliverPhysicalParentData>(app)
318            .apply_paint_transform(transform);
319    }
320
321    /// The body of Flutter's `paint` override.
322    fn paint(
323        self: RenderHandle<Self>,
324        app: &mut App,
325        context: &mut PaintingContext,
326        offset: Offset,
327    ) {
328        let Some(child) = self.child(app) else {
329            return;
330        };
331        if !child.geometry(app).visible {
332            return;
333        }
334        let paint_offset = child
335            .as_object()
336            .parent_data_of::<SliverPhysicalParentData>(app)
337            .paint_offset;
338        context.paint_child(app, child.as_object(), offset + paint_offset);
339    }
340}
341
342/// Insets a [`RenderSliver`], applying padding on each side.
343///
344/// A [`RenderSliverPadding`] object wraps the [`SliverGeometry::layout_extent`] of its child. Any
345/// incoming [`crate::SliverConstraints::overlap`] is ignored and not passed on to the child.
346pub struct RenderSliverPadding {
347    render_object: RenderObjectData,
348    render_sliver: RenderSliverData,
349    child: RenderObjectWithChildData<AnyRenderSliver>,
350    padding: EdgeInsetsGeometry,
351    text_direction: Option<TextDirection>,
352    resolved_padding: Option<EdgeInsets>,
353}
354
355impl RenderSliverPadding {
356    /// Creates a render object that insets its child in a viewport.
357    ///
358    /// The `padding` argument must have non-negative insets.
359    pub fn new(
360        app: &mut App,
361        padding: EdgeInsetsGeometry,
362        text_direction: Option<TextDirection>,
363        child: Option<AnyRenderSliver>,
364    ) -> RenderHandle<RenderSliverPadding> {
365        debug_assert!(padding.is_non_negative());
366        let this = RenderHandle::new_sliver(
367            app,
368            RenderSliverPadding {
369                render_object: RenderObjectData::new(),
370                render_sliver: RenderSliverData::new(),
371                child: RenderObjectWithChildData::new(),
372                padding,
373                text_direction,
374                resolved_padding: None,
375            },
376        );
377        this.set_child(app, child);
378        this
379    }
380
381    /// Flutter's `_resolve`.
382    fn resolve(self: RenderHandle<Self>, app: &mut App) {
383        if self.get(app).resolved_padding.is_some() {
384            return;
385        }
386        let this = self.get(app);
387        let resolved = this.padding.resolve(this.text_direction);
388        debug_assert!(resolved.is_non_negative());
389        self.get_mut(app).resolved_padding = Some(resolved);
390    }
391
392    /// Flutter's `_markNeedsResolution`.
393    fn mark_needs_resolution(self: RenderHandle<Self>, app: &mut App) {
394        self.get_mut(app).resolved_padding = None;
395        self.mark_needs_layout(app);
396    }
397
398    /// The amount to pad the child in each dimension.
399    ///
400    /// If this is set to an [`EdgeInsetsGeometry::Directional`] value, then
401    /// [`text_direction`](Self::text_direction) must not be `None`.
402    pub fn padding(self: RenderHandle<Self>, app: &App) -> EdgeInsetsGeometry {
403        self.get(app).padding
404    }
405
406    /// Sets [`padding`](Self::padding).
407    pub fn set_padding(self: RenderHandle<Self>, app: &mut App, value: EdgeInsetsGeometry) {
408        debug_assert!(value.is_non_negative());
409        if self.get(app).padding == value {
410            return;
411        }
412        self.get_mut(app).padding = value;
413        self.mark_needs_resolution(app);
414    }
415
416    /// The text direction with which to resolve [`padding`](Self::padding).
417    ///
418    /// This may be changed to `None`, but only after the padding has been changed to a value that
419    /// does not depend on the direction.
420    pub fn text_direction(self: RenderHandle<Self>, app: &App) -> Option<TextDirection> {
421        self.get(app).text_direction
422    }
423
424    /// Sets [`text_direction`](Self::text_direction).
425    pub fn set_text_direction(
426        self: RenderHandle<Self>,
427        app: &mut App,
428        value: Option<TextDirection>,
429    ) {
430        if self.get(app).text_direction == value {
431            return;
432        }
433        self.get_mut(app).text_direction = value;
434        self.mark_needs_resolution(app);
435    }
436}
437
438impl RenderObjectWithChildMixin for RenderSliverPadding {
439    type ChildType = AnyRenderSliver;
440
441    fn child_data(
442        self: RenderHandle<Self>,
443        app: &App,
444    ) -> &RenderObjectWithChildData<AnyRenderSliver> {
445        &self.get(app).child
446    }
447
448    fn child_data_mut(
449        self: RenderHandle<Self>,
450        app: &mut App,
451    ) -> &mut RenderObjectWithChildData<AnyRenderSliver> {
452        &mut self.get_mut(app).child
453    }
454}
455
456impl RenderSliverEdgeInsetsPadding for RenderSliverPadding {
457    fn resolved_padding(self: RenderHandle<Self>, app: &App) -> Option<EdgeInsets> {
458        self.get(app).resolved_padding
459    }
460}
461
462impl RenderObject for RenderSliverPadding {
463    crate::render_object_accessors!();
464
465    fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
466        RenderSliverEdgeInsetsPadding::setup_parent_data(self, app, child)
467    }
468
469    fn visit_children(
470        self: RenderHandle<Self>,
471        app: &App,
472        visitor: &mut dyn FnMut(AnyRenderObject),
473    ) {
474        if let Some(child) = self.child(app) {
475            visitor(child.as_object());
476        }
477    }
478
479    fn apply_paint_transform(
480        self: RenderHandle<Self>,
481        app: &App,
482        child: AnyRenderObject,
483        transform: &mut Matrix4,
484    ) {
485        RenderSliverEdgeInsetsPadding::apply_paint_transform(self, app, child, transform)
486    }
487
488    fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
489        self.resolve(app);
490        RenderSliverEdgeInsetsPadding::perform_layout(self, app)
491    }
492
493    fn paint(
494        self: RenderHandle<Self>,
495        app: &mut App,
496        context: &mut PaintingContext,
497        offset: Offset,
498    ) {
499        RenderSliverEdgeInsetsPadding::paint(self, app, context, offset)
500    }
501}
502
503impl RenderSliver for RenderSliverPadding {
504    crate::render_sliver_accessors!();
505
506    fn hit_test_children(
507        self: RenderHandle<Self>,
508        app: &mut App,
509        result: &mut SliverHitTestResult<'_>,
510        main_axis_position: f64,
511        cross_axis_position: f64,
512    ) -> bool {
513        RenderSliverEdgeInsetsPadding::hit_test_children(
514            self,
515            app,
516            result,
517            main_axis_position,
518            cross_axis_position,
519        )
520    }
521
522    fn child_main_axis_position(
523        self: RenderHandle<Self>,
524        app: &App,
525        child: AnyRenderObject,
526    ) -> f64 {
527        RenderSliverEdgeInsetsPadding::child_main_axis_position(self, app, child)
528    }
529
530    fn child_cross_axis_position(
531        self: RenderHandle<Self>,
532        app: &App,
533        child: AnyRenderObject,
534    ) -> f64 {
535        RenderSliverEdgeInsetsPadding::child_cross_axis_position(self, app, child)
536    }
537
538    fn child_scroll_offset(
539        self: RenderHandle<Self>,
540        app: &App,
541        child: AnyRenderObject,
542    ) -> Option<f64> {
543        RenderSliverEdgeInsetsPadding::child_scroll_offset(self, app, child)
544    }
545}
546
547#[cfg(test)]
548mod tests {
549    use inset_embedder::Size;
550    use inset_foundation::AppCell;
551    use inset_foundation::Handle;
552    use inset_gestures::HitTestResult;
553
554    use super::*;
555    use crate::box_::{AnyRenderBox, BoxConstraints, RenderBox};
556    use crate::pipeline_owner::PipelineOwner;
557    use crate::sliver::{RenderSliverToBoxAdapter, SliverPhysicalParentData};
558    use crate::sliver_multi_box_adaptor::test_support::hit_testable_box;
559    use crate::viewport::RenderViewport;
560    use crate::viewport_offset::{FixedViewportOffset, ViewportOffset};
561
562    /// Lays a sliver out in a viewport of the given size, without a `RenderView`.
563    fn lay_out(
564        app: &mut App,
565        sliver: AnyRenderSliver,
566        size: Size,
567        scroll_offset: f64,
568    ) -> (Handle<PipelineOwner>, RenderHandle<RenderViewport>) {
569        let offset = FixedViewportOffset::new(app, scroll_offset);
570        let viewport = RenderViewport::new(
571            app,
572            AxisDirection::Right,
573            offset.as_viewport_offset(),
574            Some(vec![sliver]),
575            None,
576        );
577        let owner = PipelineOwner::new(app, None, None);
578        owner.set_root_node(app, Some(viewport.as_object()));
579        viewport
580            .as_box()
581            .layout(app, BoxConstraints::tight(size), false);
582        (owner, viewport)
583    }
584
585    fn padded_box(
586        app: &mut App,
587        padding: EdgeInsetsGeometry,
588        height: f64,
589    ) -> (RenderHandle<RenderSliverPadding>, AnyRenderBox) {
590        let child_box = hit_testable_box(app, height);
591        let adapter = RenderSliverToBoxAdapter::new(app, Some(child_box));
592        let padding = RenderSliverPadding::new(
593            app,
594            padding,
595            Some(TextDirection::Ltr),
596            Some(adapter.as_sliver()),
597        );
598        (padding, child_box)
599    }
600
601    /// `sliver_padding_test.dart`: the padding adds to the sliver's scroll extent, and the child
602    /// is offset by the leading padding.
603    #[test]
604    fn padding_insets_the_child_and_grows_the_scroll_extent() {
605        let cell = AppCell::new();
606        let mut app = cell.borrow_mut();
607        let (padding, child_box) = padded_box(
608            &mut app,
609            EdgeInsetsGeometry::from_ltrb(10.0, 20.0, 30.0, 40.0),
610            100.0,
611        );
612        lay_out(&mut app, padding.as_sliver(), Size::new(200.0, 400.0), 0.0);
613
614        let geometry = padding.geometry(&app);
615        assert_eq!(geometry.scroll_extent, 160.0);
616        assert_eq!(geometry.paint_extent, 160.0);
617        // The child is squeezed in the cross axis by the horizontal padding.
618        assert_eq!(child_box.size(&app), Size::new(160.0, 100.0));
619        assert_eq!(padding.before_padding(&app), 20.0);
620        assert_eq!(padding.after_padding(&app), 40.0);
621        assert_eq!(padding.main_axis_padding(&app), 60.0);
622        assert_eq!(padding.cross_axis_padding(&app), 40.0);
623        let child = padding.child(&app).expect("has a child");
624        assert_eq!(
625            child
626                .as_object()
627                .parent_data_of::<SliverPhysicalParentData>(&app)
628                .paint_offset,
629            Offset::new(10.0, 20.0)
630        );
631        assert_eq!(
632            RenderSliver::child_scroll_offset(padding, &app, child.as_object()),
633            Some(20.0)
634        );
635    }
636
637    /// Scrolling past the leading padding consumes it before the child starts to scroll.
638    #[test]
639    fn scrolling_consumes_the_leading_padding_first() {
640        let cell = AppCell::new();
641        let mut app = cell.borrow_mut();
642        let (padding, _) = padded_box(
643            &mut app,
644            EdgeInsetsGeometry::from_ltrb(0.0, 20.0, 0.0, 0.0),
645            100.0,
646        );
647        lay_out(&mut app, padding.as_sliver(), Size::new(200.0, 400.0), 10.0);
648
649        let child = padding.child(&app).expect("has a child");
650        // Only half the leading padding has scrolled off, so the child is still at its start.
651        assert_eq!(child.constraints(&app).scroll_offset, 0.0);
652        assert_eq!(
653            RenderSliverEdgeInsetsPadding::child_main_axis_position(
654                padding,
655                &app,
656                child.as_object()
657            ),
658            10.0
659        );
660        assert_eq!(padding.geometry(&app).scroll_extent, 120.0);
661    }
662
663    /// A padding sliver with no child is just the padding.
664    #[test]
665    fn a_childless_padding_is_the_padding_alone() {
666        let cell = AppCell::new();
667        let mut app = cell.borrow_mut();
668        let padding = RenderSliverPadding::new(
669            &mut app,
670            EdgeInsetsGeometry::all(15.0),
671            Some(TextDirection::Ltr),
672            None,
673        );
674        lay_out(&mut app, padding.as_sliver(), Size::new(200.0, 400.0), 0.0);
675
676        let geometry = padding.geometry(&app);
677        assert_eq!(geometry.scroll_extent, 30.0);
678        assert_eq!(geometry.paint_extent, 30.0);
679        assert_eq!(geometry.max_paint_extent, 30.0);
680    }
681
682    /// A hit inside the child, past the leading padding, reaches it.
683    #[test]
684    fn padding_hit_tests_its_child_at_the_inset_position() {
685        let cell = AppCell::new();
686        let mut app = cell.borrow_mut();
687        let (padding, _) = padded_box(
688            &mut app,
689            EdgeInsetsGeometry::from_ltrb(10.0, 20.0, 30.0, 40.0),
690            100.0,
691        );
692        lay_out(&mut app, padding.as_sliver(), Size::new(200.0, 400.0), 0.0);
693
694        let mut inside = HitTestResult::new();
695        let hit_inside = padding.hit_test(
696            &mut app,
697            &mut SliverHitTestResult::wrap(&mut inside),
698            25.0,
699            20.0,
700        );
701        let mut in_padding = HitTestResult::new();
702        let hit_in_padding = padding.hit_test(
703            &mut app,
704            &mut SliverHitTestResult::wrap(&mut in_padding),
705            5.0,
706            20.0,
707        );
708
709        let child = padding.child(&app).expect("has a child");
710        // The cross axis is inset by the left padding.
711        assert_eq!(
712            RenderSliver::child_cross_axis_position(padding, &app, child.as_object()),
713            10.0
714        );
715        // A hit past the leading padding reaches the box; one inside the padding does not.
716        assert!(hit_inside);
717        assert_eq!(inside.path().len(), 3);
718        assert!(!hit_in_padding);
719    }
720
721    /// Changing the padding re-resolves it and marks the sliver for layout.
722    #[test]
723    fn setting_the_padding_re_resolves_it() {
724        let cell = AppCell::new();
725        let mut app = cell.borrow_mut();
726        let (padding, _) = padded_box(&mut app, EdgeInsetsGeometry::all(10.0), 100.0);
727        lay_out(&mut app, padding.as_sliver(), Size::new(200.0, 400.0), 0.0);
728        assert_eq!(padding.resolved_padding(&app), Some(EdgeInsets::all(10.0)));
729
730        padding.set_padding(&mut app, EdgeInsetsGeometry::all(25.0));
731
732        assert_eq!(padding.resolved_padding(&app), None);
733        assert!(padding.as_object().debug_needs_layout(&app));
734    }
735}