1use 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
20pub trait RenderSliverEdgeInsetsPadding:
25 RenderSliver + RenderObjectWithChildMixin<ChildType = AnyRenderSliver>
26{
27 fn resolved_padding(self: RenderHandle<Self>, app: &App) -> Option<EdgeInsets>;
34
35 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 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 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 match self.constraints(app).axis() {
84 Axis::Horizontal => padding.horizontal(),
85 Axis::Vertical => padding.vertical(),
86 }
87 }
88
89 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 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 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 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 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 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 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 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 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
342pub 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 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 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 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 pub fn padding(self: RenderHandle<Self>, app: &App) -> EdgeInsetsGeometry {
403 self.get(app).padding
404 }
405
406 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 pub fn text_direction(self: RenderHandle<Self>, app: &App) -> Option<TextDirection> {
421 self.get(app).text_direction
422 }
423
424 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 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 #[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 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 #[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 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 #[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 #[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 assert_eq!(
712 RenderSliver::child_cross_axis_position(padding, &app, child.as_object()),
713 10.0
714 );
715 assert!(hit_inside);
717 assert_eq!(inside.path().len(), 3);
718 assert!(!hit_in_padding);
719 }
720
721 #[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}