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