1use std::rc::Rc;
6use std::time::Duration;
7
8use inset_animation::{
9 Animatable, AnimationController, AnimationStatus, AnyAnimation, Curve, CurveTween, Curves,
10 Tween,
11};
12use inset_embedder::{Matrix4, Offset, Rect, clamp_double};
13use inset_foundation::{App, AsyncCallback, Handle, HandleId, ListenableObject, Listener};
14use inset_painting::{Axis, AxisDirection, transform_rect};
15use inset_scheduler::{Ticker, TickerCallback, TickerProvider, TickerProviderObject};
16
17use crate::box_::{AnyRenderBox, BoxHitTestResult};
18use crate::object::{
19 AnyRenderObject, RenderHandle, RenderObjectBase, RenderObjectWithChildMixin, resolve,
20};
21use crate::painting_context::PaintingContext;
22use crate::sliver::{
23 RenderSliver, RenderSliverHelpers, SliverGeometry, SliverHitTestResult,
24 apply_growth_direction_to_axis_direction, calculate_cache_offset,
25};
26use crate::viewport_offset::ScrollDirection;
27
28fn trim(original: Option<Rect>, top: f64, right: f64, bottom: f64, left: f64) -> Option<Rect> {
32 original.map(|rect| rect.intersect(Rect::from_ltrb(left, top, right, bottom)))
33}
34
35#[derive(Clone)]
39pub struct TickerProviderRef(Rc<dyn TickerProvider>);
40
41impl TickerProviderRef {
42 pub fn new<T: TickerProviderObject>(provider: Handle<T>) -> TickerProviderRef {
44 TickerProviderRef(Rc::new(provider))
45 }
46}
47
48impl TickerProvider for TickerProviderRef {
49 fn create_ticker(&self, app: &mut App, on_tick: TickerCallback) -> Handle<Ticker> {
50 self.0.create_ticker(app, on_tick)
51 }
52}
53
54impl PartialEq for TickerProviderRef {
55 fn eq(&self, other: &TickerProviderRef) -> bool {
56 Rc::ptr_eq(&self.0, &other.0)
57 }
58}
59
60impl std::fmt::Debug for TickerProviderRef {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 f.write_str("TickerProviderRef")
63 }
64}
65
66#[derive(Clone)]
68pub struct OverScrollHeaderStretchConfiguration {
69 pub stretch_trigger_offset: f64,
72
73 pub on_stretch_trigger: Option<AsyncCallback>,
76}
77
78impl OverScrollHeaderStretchConfiguration {
79 pub fn new() -> OverScrollHeaderStretchConfiguration {
84 OverScrollHeaderStretchConfiguration {
85 stretch_trigger_offset: 100.0,
86 on_stretch_trigger: None,
87 }
88 }
89
90 pub fn stretch_trigger_offset(mut self, value: f64) -> OverScrollHeaderStretchConfiguration {
92 self.stretch_trigger_offset = value;
93 self
94 }
95
96 pub fn on_stretch_trigger(
98 mut self,
99 value: AsyncCallback,
100 ) -> OverScrollHeaderStretchConfiguration {
101 self.on_stretch_trigger = Some(value);
102 self
103 }
104}
105
106impl Default for OverScrollHeaderStretchConfiguration {
107 fn default() -> OverScrollHeaderStretchConfiguration {
108 OverScrollHeaderStretchConfiguration::new()
109 }
110}
111
112#[derive(Clone, Copy, Debug, PartialEq)]
115pub struct PersistentHeaderShowOnScreenConfiguration {
116 pub min_show_on_screen_extent: f64,
128
129 pub max_show_on_screen_extent: f64,
137}
138
139impl PersistentHeaderShowOnScreenConfiguration {
140 pub const fn new() -> PersistentHeaderShowOnScreenConfiguration {
143 PersistentHeaderShowOnScreenConfiguration {
144 min_show_on_screen_extent: f64::NEG_INFINITY,
145 max_show_on_screen_extent: f64::INFINITY,
146 }
147 }
148
149 pub fn min_show_on_screen_extent(
151 mut self,
152 value: f64,
153 ) -> PersistentHeaderShowOnScreenConfiguration {
154 self.min_show_on_screen_extent = value;
155 debug_assert!(self.min_show_on_screen_extent <= self.max_show_on_screen_extent);
156 self
157 }
158
159 pub fn max_show_on_screen_extent(
161 mut self,
162 value: f64,
163 ) -> PersistentHeaderShowOnScreenConfiguration {
164 self.max_show_on_screen_extent = value;
165 debug_assert!(self.min_show_on_screen_extent <= self.max_show_on_screen_extent);
166 self
167 }
168}
169
170impl Default for PersistentHeaderShowOnScreenConfiguration {
171 fn default() -> PersistentHeaderShowOnScreenConfiguration {
172 PersistentHeaderShowOnScreenConfiguration::new()
173 }
174}
175
176#[derive(Clone)]
178pub struct FloatingHeaderSnapConfiguration {
179 pub curve: Rc<dyn Curve>,
181
182 pub duration: Duration,
184}
185
186impl FloatingHeaderSnapConfiguration {
187 pub fn new() -> FloatingHeaderSnapConfiguration {
192 FloatingHeaderSnapConfiguration {
193 curve: Curves::ease(),
194 duration: Duration::from_millis(300),
195 }
196 }
197
198 pub fn curve(mut self, value: Rc<dyn Curve>) -> FloatingHeaderSnapConfiguration {
200 self.curve = value;
201 self
202 }
203
204 pub fn duration(mut self, value: Duration) -> FloatingHeaderSnapConfiguration {
206 self.duration = value;
207 self
208 }
209}
210
211impl Default for FloatingHeaderSnapConfiguration {
212 fn default() -> FloatingHeaderSnapConfiguration {
213 FloatingHeaderSnapConfiguration::new()
214 }
215}
216
217pub struct RenderSliverPersistentHeaderData {
219 last_stretch_offset: f64,
220 needs_update_child: bool,
221 last_shrink_offset: f64,
222 last_overlaps_content: bool,
223 pub stretch_configuration: Option<OverScrollHeaderStretchConfiguration>,
227}
228
229impl RenderSliverPersistentHeaderData {
230 pub fn new(
232 stretch_configuration: Option<OverScrollHeaderStretchConfiguration>,
233 ) -> RenderSliverPersistentHeaderData {
234 RenderSliverPersistentHeaderData {
235 last_stretch_offset: 0.0,
236 needs_update_child: true,
237 last_shrink_offset: 0.0,
238 last_overlaps_content: false,
239 stretch_configuration,
240 }
241 }
242}
243
244#[macro_export]
246macro_rules! render_sliver_persistent_header_accessors {
247 () => {
248 fn header_data(
249 self: $crate::RenderHandle<Self>,
250 app: &::inset_foundation::App,
251 ) -> &$crate::RenderSliverPersistentHeaderData {
252 &self.get(app).header
253 }
254 fn header_data_mut(
255 self: $crate::RenderHandle<Self>,
256 app: &mut ::inset_foundation::App,
257 ) -> &mut $crate::RenderSliverPersistentHeaderData {
258 &mut self.get_mut(app).header
259 }
260 };
261}
262
263pub trait RenderSliverPersistentHeader:
279 RenderSliver + RenderObjectWithChildMixin<ChildType = AnyRenderBox> + RenderSliverHelpers
280{
281 fn header_data(self: RenderHandle<Self>, app: &App) -> &RenderSliverPersistentHeaderData;
283
284 fn header_data_mut(
286 self: RenderHandle<Self>,
287 app: &mut App,
288 ) -> &mut RenderSliverPersistentHeaderData;
289
290 fn max_extent(self: RenderHandle<Self>, app: &App) -> f64;
294
295 fn min_extent(self: RenderHandle<Self>, app: &App) -> f64;
300
301 fn child_extent(self: RenderHandle<Self>, app: &App) -> f64 {
303 let Some(child) = self.child(app) else {
304 return 0.0;
305 };
306 debug_assert!(child.has_size(app));
307 match self.constraints(app).axis() {
308 Axis::Vertical => child.size(app).height(),
309 Axis::Horizontal => child.size(app).width(),
310 }
311 }
312
313 fn last_shrink_offset(self: RenderHandle<Self>, app: &App) -> f64 {
315 self.header_data(app).last_shrink_offset
316 }
317
318 fn last_overlaps_content(self: RenderHandle<Self>, app: &App) -> bool {
320 self.header_data(app).last_overlaps_content
321 }
322
323 fn stretch_configuration(
325 self: RenderHandle<Self>,
326 app: &App,
327 ) -> Option<OverScrollHeaderStretchConfiguration> {
328 self.header_data(app).stretch_configuration.clone()
329 }
330
331 fn set_stretch_configuration(
333 self: RenderHandle<Self>,
334 app: &mut App,
335 value: Option<OverScrollHeaderStretchConfiguration>,
336 ) {
337 self.header_data_mut(app).stretch_configuration = value;
338 }
339
340 fn update_child(
352 self: RenderHandle<Self>,
353 app: &mut App,
354 shrink_offset: f64,
355 overlaps_content: bool,
356 ) {
357 let _ = (self, app, shrink_offset, overlaps_content);
358 }
359
360 fn mark_needs_layout(self: RenderHandle<Self>, app: &mut App) {
365 self.header_data_mut(app).needs_update_child = true;
366 RenderSliver::mark_needs_layout(self, app);
367 }
368
369 fn layout_child(
377 self: RenderHandle<Self>,
378 app: &mut App,
379 scroll_offset: f64,
380 max_extent: f64,
381 overlaps_content: bool,
382 ) {
383 let shrink_offset = scroll_offset.min(max_extent);
384 let header = self.header_data(app);
385 if header.needs_update_child
386 || header.last_shrink_offset != shrink_offset
387 || header.last_overlaps_content != overlaps_content
388 {
389 self.as_object().invoke_layout_callback(app, |app| {
390 self.update_child(app, shrink_offset, overlaps_content);
391 });
392 let header = self.header_data_mut(app);
393 header.last_shrink_offset = shrink_offset;
394 header.last_overlaps_content = overlaps_content;
395 header.needs_update_child = false;
396 }
397 debug_assert!(
398 self.min_extent(app) <= max_extent,
399 "the max_extent for this persistent header is less than its min_extent"
400 );
401 let constraints = self.constraints(app);
402 let stretch_configuration = self.stretch_configuration(app);
403 let mut stretch_offset = 0.0;
404 if stretch_configuration.is_some() && constraints.scroll_offset == 0.0 {
405 stretch_offset += constraints.overlap.abs();
406 }
407
408 if let Some(child) = self.child(app) {
409 let min_extent = self.min_extent(app);
410 let child_constraints = constraints.as_box_constraints(
411 0.0,
412 min_extent.max(max_extent - shrink_offset) + stretch_offset,
413 None,
414 );
415 child.layout(app, child_constraints, true);
416 }
417
418 if let Some(configuration) = &stretch_configuration
419 && let Some(on_stretch_trigger) = configuration.on_stretch_trigger.clone()
420 && stretch_offset >= configuration.stretch_trigger_offset
421 && self.header_data(app).last_stretch_offset <= configuration.stretch_trigger_offset
422 {
423 drop(on_stretch_trigger(app));
425 }
426 self.header_data_mut(app).last_stretch_offset = stretch_offset;
427 }
428
429 fn hit_test_children(
431 self: RenderHandle<Self>,
432 app: &mut App,
433 result: &mut SliverHitTestResult<'_>,
434 main_axis_position: f64,
435 cross_axis_position: f64,
436 ) -> bool {
437 debug_assert!(self.geometry(app).hit_test_extent > 0.0);
438 let Some(child) = self.child(app) else {
439 return false;
440 };
441 self.hit_test_box_child(
442 app,
443 &mut BoxHitTestResult::wrap(result),
444 child,
445 main_axis_position,
446 cross_axis_position,
447 )
448 }
449
450 fn apply_paint_transform(
452 self: RenderHandle<Self>,
453 app: &App,
454 child: AnyRenderObject,
455 transform: &mut Matrix4,
456 ) {
457 debug_assert_eq!(Some(child), self.child(app).map(AnyRenderBox::as_object));
458 let child = child.as_box().expect("the child is a box");
459 self.apply_paint_transform_for_box_child(app, child, transform);
460 }
461
462 fn paint(
464 self: RenderHandle<Self>,
465 app: &mut App,
466 context: &mut PaintingContext,
467 offset: Offset,
468 ) {
469 let Some(child) = self.child(app) else {
470 return;
471 };
472 if !self.geometry(app).visible {
473 return;
474 }
475 let constraints = self.constraints(app);
476 let paint_extent = self.geometry(app).paint_extent;
477 let child_position = RenderSliver::child_main_axis_position(self, app, child.as_object());
478 let child_extent = self.child_extent(app);
479 let offset = offset
480 + match apply_growth_direction_to_axis_direction(
481 constraints.axis_direction,
482 constraints.growth_direction,
483 ) {
484 AxisDirection::Up => Offset::new(0.0, paint_extent - child_position - child_extent),
485 AxisDirection::Left => {
486 Offset::new(paint_extent - child_position - child_extent, 0.0)
487 }
488 AxisDirection::Right => Offset::new(child_position, 0.0),
489 AxisDirection::Down => Offset::new(0.0, child_position),
490 };
491 context.paint_child(app, child.as_object(), offset);
492 }
493}
494
495#[derive(Debug, Default)]
497pub struct RenderSliverScrollingPersistentHeaderData {
498 child_position: Option<f64>,
501}
502
503impl RenderSliverScrollingPersistentHeaderData {
504 pub const fn new() -> RenderSliverScrollingPersistentHeaderData {
506 RenderSliverScrollingPersistentHeaderData {
507 child_position: None,
508 }
509 }
510}
511
512pub trait RenderSliverScrollingPersistentHeader: RenderSliverPersistentHeader {
517 fn scrolling_data(
519 self: RenderHandle<Self>,
520 app: &App,
521 ) -> &RenderSliverScrollingPersistentHeaderData;
522
523 fn scrolling_data_mut(
525 self: RenderHandle<Self>,
526 app: &mut App,
527 ) -> &mut RenderSliverScrollingPersistentHeaderData;
528
529 fn update_geometry(self: RenderHandle<Self>, app: &mut App) -> f64 {
532 let constraints = self.constraints(app);
533 let mut stretch_offset = 0.0;
534 if self.stretch_configuration(app).is_some() {
535 stretch_offset += constraints.overlap.abs();
536 }
537 let max_extent = self.max_extent(app);
538 let paint_extent = max_extent - constraints.scroll_offset;
539 let cache_extent = calculate_cache_offset(constraints, 0.0, max_extent);
540
541 self.set_geometry(
542 app,
543 SliverGeometry::builder()
544 .scroll_extent(max_extent)
545 .paint_origin(constraints.overlap.min(0.0))
546 .paint_extent(clamp_double(
547 paint_extent,
548 0.0,
549 constraints.remaining_paint_extent,
550 ))
551 .cache_extent(cache_extent)
552 .max_paint_extent(max_extent + stretch_offset)
553 .has_visual_overflow(true)
555 .build(),
556 );
557 if stretch_offset > 0.0 {
558 0.0
559 } else {
560 (paint_extent - self.child_extent(app)).min(0.0)
561 }
562 }
563
564 fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
566 let scroll_offset = self.constraints(app).scroll_offset;
567 let max_extent = self.max_extent(app);
568 self.layout_child(app, scroll_offset, max_extent, false);
569 let child_position = self.update_geometry(app);
570 self.scrolling_data_mut(app).child_position = Some(child_position);
571 }
572
573 fn child_main_axis_position(
575 self: RenderHandle<Self>,
576 app: &App,
577 child: AnyRenderObject,
578 ) -> f64 {
579 debug_assert_eq!(Some(child), self.child(app).map(AnyRenderBox::as_object));
580 self.scrolling_data(app)
581 .child_position
582 .expect("the header has been laid out")
583 }
584}
585
586#[derive(Clone, Copy, Debug)]
588pub struct RenderSliverPinnedPersistentHeaderData {
589 pub show_on_screen_configuration: Option<PersistentHeaderShowOnScreenConfiguration>,
594}
595
596impl RenderSliverPinnedPersistentHeaderData {
597 pub const fn new() -> RenderSliverPinnedPersistentHeaderData {
599 RenderSliverPinnedPersistentHeaderData {
600 show_on_screen_configuration: Some(PersistentHeaderShowOnScreenConfiguration::new()),
601 }
602 }
603}
604
605impl Default for RenderSliverPinnedPersistentHeaderData {
606 fn default() -> RenderSliverPinnedPersistentHeaderData {
607 RenderSliverPinnedPersistentHeaderData::new()
608 }
609}
610
611pub trait RenderSliverPinnedPersistentHeader: RenderSliverPersistentHeader {
617 fn pinned_data(self: RenderHandle<Self>, app: &App) -> &RenderSliverPinnedPersistentHeaderData;
619
620 fn pinned_data_mut(
622 self: RenderHandle<Self>,
623 app: &mut App,
624 ) -> &mut RenderSliverPinnedPersistentHeaderData;
625
626 fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
628 let constraints = self.constraints(app);
629 let max_extent = self.max_extent(app);
630 let overlaps_content = constraints.overlap > 0.0;
631 self.layout_child(app, constraints.scroll_offset, max_extent, overlaps_content);
632 let effective_remaining_paint_extent =
633 (constraints.remaining_paint_extent - constraints.overlap).max(0.0);
634 let layout_extent = clamp_double(
635 max_extent - constraints.scroll_offset,
636 0.0,
637 effective_remaining_paint_extent,
638 );
639 let stretch_offset = if self.stretch_configuration(app).is_some() {
640 constraints.overlap.abs()
641 } else {
642 0.0
643 };
644 let child_extent = self.child_extent(app);
645 let min_extent = self.min_extent(app);
646 self.set_geometry(
647 app,
648 SliverGeometry::builder()
649 .scroll_extent(max_extent)
650 .paint_origin(constraints.overlap)
651 .paint_extent(child_extent.min(effective_remaining_paint_extent))
652 .layout_extent(layout_extent)
653 .max_paint_extent(max_extent + stretch_offset)
654 .max_scroll_obstruction_extent(min_extent)
655 .cache_extent(if layout_extent > 0.0 {
656 -constraints.cache_origin + layout_extent
657 } else {
658 layout_extent
659 })
660 .has_visual_overflow(true)
662 .build(),
663 );
664 }
665
666 fn child_main_axis_position(
668 self: RenderHandle<Self>,
669 _app: &App,
670 _child: AnyRenderObject,
671 ) -> f64 {
672 let _ = self;
673 0.0
674 }
675
676 fn show_on_screen(
678 self: RenderHandle<Self>,
679 app: &mut App,
680 descendant: Option<AnyRenderObject>,
681 rect: Option<Rect>,
682 duration: Duration,
683 curve: Rc<dyn Curve>,
684 ) {
685 let local_bounds = match descendant {
686 Some(descendant) => Some(transform_rect(
687 &descendant.get_transform_to(app, Some(self.as_object())),
688 rect.unwrap_or_else(|| descendant.paint_bounds(app)),
689 )),
690 None => rect,
691 };
692
693 let constraints = self.constraints(app);
694 let child_extent = self.child_extent(app);
695 let new_rect = match apply_growth_direction_to_axis_direction(
696 constraints.axis_direction,
697 constraints.growth_direction,
698 ) {
699 AxisDirection::Up => trim(
700 local_bounds,
701 f64::NEG_INFINITY,
702 f64::INFINITY,
703 child_extent,
704 f64::NEG_INFINITY,
705 ),
706 AxisDirection::Left => trim(
707 local_bounds,
708 f64::NEG_INFINITY,
709 child_extent,
710 f64::INFINITY,
711 f64::NEG_INFINITY,
712 ),
713 AxisDirection::Right => trim(
714 local_bounds,
715 f64::NEG_INFINITY,
716 f64::INFINITY,
717 f64::INFINITY,
718 0.0,
719 ),
720 AxisDirection::Down => trim(
721 local_bounds,
722 0.0,
723 f64::INFINITY,
724 f64::INFINITY,
725 f64::NEG_INFINITY,
726 ),
727 };
728
729 RenderObjectBase::show_on_screen(
730 self,
731 app,
732 Some(self.as_object()),
733 new_rect,
734 duration,
735 curve,
736 );
737 }
738}
739
740pub struct RenderSliverFloatingPersistentHeaderData {
742 controller: Option<Handle<AnimationController>>,
743 animation: Option<AnyAnimation<f64>>,
744 last_actual_scroll_offset: Option<f64>,
745 effective_scroll_offset: Option<f64>,
746 last_started_scroll_direction: Option<ScrollDirection>,
750 child_position: Option<f64>,
753 vsync: Option<TickerProviderRef>,
754 pub snap_configuration: Option<FloatingHeaderSnapConfiguration>,
758 pub show_on_screen_configuration: Option<PersistentHeaderShowOnScreenConfiguration>,
763}
764
765impl RenderSliverFloatingPersistentHeaderData {
766 pub fn new(
768 vsync: Option<TickerProviderRef>,
769 snap_configuration: Option<FloatingHeaderSnapConfiguration>,
770 show_on_screen_configuration: Option<PersistentHeaderShowOnScreenConfiguration>,
771 ) -> RenderSliverFloatingPersistentHeaderData {
772 RenderSliverFloatingPersistentHeaderData {
773 controller: None,
774 animation: None,
775 last_actual_scroll_offset: None,
776 effective_scroll_offset: None,
777 last_started_scroll_direction: None,
778 child_position: None,
779 vsync,
780 snap_configuration,
781 show_on_screen_configuration,
782 }
783 }
784}
785
786pub trait RenderSliverFloatingPersistentHeader: RenderSliverPersistentHeader {
795 fn floating_data(
797 self: RenderHandle<Self>,
798 app: &App,
799 ) -> &RenderSliverFloatingPersistentHeaderData;
800
801 fn floating_data_mut(
803 self: RenderHandle<Self>,
804 app: &mut App,
805 ) -> &mut RenderSliverFloatingPersistentHeaderData;
806
807 fn did_detach(self: RenderHandle<Self>, app: &mut App) {
810 if let Some(controller) = self.floating_data(app).controller {
811 controller.dispose(app);
812 }
813 self.floating_data_mut(app).controller = None;
814 }
815
816 fn vsync(self: RenderHandle<Self>, app: &App) -> Option<TickerProviderRef> {
818 self.floating_data(app).vsync.clone()
819 }
820
821 fn set_vsync(self: RenderHandle<Self>, app: &mut App, value: Option<TickerProviderRef>) {
823 if value == self.floating_data(app).vsync {
824 return;
825 }
826 self.floating_data_mut(app).vsync = value.clone();
827 match value {
828 None => {
829 if let Some(controller) = self.floating_data(app).controller {
830 controller.dispose(app);
831 }
832 self.floating_data_mut(app).controller = None;
833 }
834 Some(vsync) => {
835 if let Some(controller) = self.floating_data(app).controller {
836 controller.resync(app, vsync);
837 }
838 }
839 }
840 }
841
842 fn snap_configuration(
844 self: RenderHandle<Self>,
845 app: &App,
846 ) -> Option<FloatingHeaderSnapConfiguration> {
847 self.floating_data(app).snap_configuration.clone()
848 }
849
850 fn set_snap_configuration(
852 self: RenderHandle<Self>,
853 app: &mut App,
854 value: Option<FloatingHeaderSnapConfiguration>,
855 ) {
856 self.floating_data_mut(app).snap_configuration = value;
857 }
858
859 fn show_on_screen_configuration(
861 self: RenderHandle<Self>,
862 app: &App,
863 ) -> Option<PersistentHeaderShowOnScreenConfiguration> {
864 self.floating_data(app).show_on_screen_configuration
865 }
866
867 fn set_show_on_screen_configuration(
869 self: RenderHandle<Self>,
870 app: &mut App,
871 value: Option<PersistentHeaderShowOnScreenConfiguration>,
872 ) {
873 self.floating_data_mut(app).show_on_screen_configuration = value;
874 }
875
876 fn effective_scroll_offset(self: RenderHandle<Self>, app: &App) -> Option<f64> {
878 self.floating_data(app).effective_scroll_offset
879 }
880
881 fn update_geometry(self: RenderHandle<Self>, app: &mut App) -> f64 {
884 let constraints = self.constraints(app);
885 let mut stretch_offset = 0.0;
886 if self.stretch_configuration(app).is_some() {
887 stretch_offset += constraints.overlap.abs();
888 }
889 let max_extent = self.max_extent(app);
890 let paint_extent = max_extent
891 - self
892 .effective_scroll_offset(app)
893 .expect("perform_layout sets the effective scroll offset");
894 let layout_extent = max_extent - constraints.scroll_offset;
895 self.set_geometry(
896 app,
897 SliverGeometry::builder()
898 .scroll_extent(max_extent)
899 .paint_origin(constraints.overlap.min(0.0))
900 .paint_extent(clamp_double(
901 paint_extent,
902 0.0,
903 constraints.remaining_paint_extent,
904 ))
905 .layout_extent(clamp_double(
906 layout_extent,
907 0.0,
908 constraints.remaining_paint_extent,
909 ))
910 .max_paint_extent(max_extent + stretch_offset)
911 .has_visual_overflow(true)
913 .build(),
914 );
915 if stretch_offset > 0.0 {
916 0.0
917 } else {
918 (paint_extent - self.child_extent(app)).min(0.0)
919 }
920 }
921
922 fn update_animation(
924 self: RenderHandle<Self>,
925 app: &mut App,
926 duration: Duration,
927 end_value: f64,
928 curve: Rc<dyn Curve>,
929 ) {
930 let vsync = self
931 .vsync(app)
932 .expect("vsync must be set if the floating header changes size animatedly");
933 let controller = match self.floating_data(app).controller {
934 Some(controller) => controller,
935 None => {
936 let controller = AnimationController::create(
937 app,
938 None,
939 Some(duration),
940 None,
941 0.0,
942 1.0,
943 inset_animation::AnimationBehavior::Normal,
944 vsync,
945 );
946 self.floating_data_mut(app).controller = Some(controller);
947 controller.add_listener(
948 app,
949 Listener::handle_method(self.handle(), snap_tick::<Self>),
950 );
951 controller
952 }
953 };
954 let begin = self.effective_scroll_offset(app);
955 let tween = Tween::new(app, begin, Some(end_value));
956 let curve_tween = CurveTween::new(app, curve);
957 let animation = controller.drive(app, tween.chain(curve_tween));
958 self.floating_data_mut(app).animation = Some(animation);
959 }
960
961 fn update_scroll_start_direction(
963 self: RenderHandle<Self>,
964 app: &mut App,
965 direction: ScrollDirection,
966 ) {
967 self.floating_data_mut(app).last_started_scroll_direction = Some(direction);
968 }
969
970 fn maybe_start_snap_animation(
972 self: RenderHandle<Self>,
973 app: &mut App,
974 direction: ScrollDirection,
975 ) {
976 let Some(snap) = self.snap_configuration(app) else {
977 return;
978 };
979 let effective_scroll_offset = self
980 .effective_scroll_offset(app)
981 .expect("the header has been laid out");
982 let max_extent = self.max_extent(app);
983 if direction == ScrollDirection::Forward && effective_scroll_offset <= 0.0 {
984 return;
985 }
986 if direction == ScrollDirection::Reverse && effective_scroll_offset >= max_extent {
987 return;
988 }
989
990 let end_value = if direction == ScrollDirection::Forward {
991 0.0
992 } else {
993 max_extent
994 };
995 self.update_animation(app, snap.duration, end_value, Rc::clone(&snap.curve));
996 if let Some(controller) = self.floating_data(app).controller {
997 controller.forward(app, Some(0.0));
998 }
999 }
1000
1001 fn maybe_stop_snap_animation(
1003 self: RenderHandle<Self>,
1004 app: &mut App,
1005 _direction: ScrollDirection,
1006 ) {
1007 if let Some(controller) = self.floating_data(app).controller {
1008 controller.stop(app, false);
1009 }
1010 }
1011
1012 fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
1014 let constraints = self.constraints(app);
1015 let max_extent = self.max_extent(app);
1016 let data = self.floating_data(app);
1017 let last_actual_scroll_offset = data.last_actual_scroll_offset;
1018 let effective_scroll_offset = data.effective_scroll_offset;
1019 let last_started_scroll_direction = data.last_started_scroll_direction;
1020 let new_effective_scroll_offset = match last_actual_scroll_offset {
1024 Some(last_actual_scroll_offset)
1025 if constraints.scroll_offset < last_actual_scroll_offset
1026 || effective_scroll_offset.is_some_and(|offset| offset < max_extent) =>
1027 {
1028 let mut delta = last_actual_scroll_offset - constraints.scroll_offset;
1029 let mut effective_scroll_offset =
1030 effective_scroll_offset.expect("laid out at least once");
1031 let allow_floating_expansion = constraints.user_scroll_direction
1032 == ScrollDirection::Forward
1033 || last_started_scroll_direction == Some(ScrollDirection::Forward);
1034 if allow_floating_expansion {
1035 if effective_scroll_offset > max_extent {
1036 effective_scroll_offset = max_extent;
1039 }
1040 } else if delta > 0.0 {
1041 delta = 0.0;
1043 }
1044 clamp_double(
1045 effective_scroll_offset - delta,
1046 0.0,
1047 constraints.scroll_offset,
1048 )
1049 }
1050 _ => constraints.scroll_offset,
1051 };
1052 self.floating_data_mut(app).effective_scroll_offset = Some(new_effective_scroll_offset);
1053 let overlaps_content = new_effective_scroll_offset < constraints.scroll_offset;
1054
1055 self.layout_child(
1056 app,
1057 new_effective_scroll_offset,
1058 max_extent,
1059 overlaps_content,
1060 );
1061 let child_position = self.update_geometry(app);
1062 let data = self.floating_data_mut(app);
1063 data.child_position = Some(child_position);
1064 data.last_actual_scroll_offset = Some(constraints.scroll_offset);
1065 }
1066
1067 fn show_on_screen(
1069 self: RenderHandle<Self>,
1070 app: &mut App,
1071 descendant: Option<AnyRenderObject>,
1072 rect: Option<Rect>,
1073 duration: Duration,
1074 curve: Rc<dyn Curve>,
1075 ) {
1076 let Some(show_on_screen) = self.show_on_screen_configuration(app) else {
1077 return RenderObjectBase::show_on_screen(self, app, descendant, rect, duration, curve);
1078 };
1079
1080 let child = self.child(app);
1081 debug_assert!(child.is_some() || descendant.is_none());
1082 let child_bounds = match descendant {
1088 Some(descendant) => Some(transform_rect(
1089 &descendant.get_transform_to(app, child.map(AnyRenderBox::as_object)),
1090 rect.unwrap_or_else(|| descendant.paint_bounds(app)),
1091 )),
1092 None => rect,
1093 };
1094
1095 let constraints = self.constraints(app);
1096 let child_extent = self.child_extent(app);
1097 let (mut target_extent, target_rect) = match apply_growth_direction_to_axis_direction(
1098 constraints.axis_direction,
1099 constraints.growth_direction,
1100 ) {
1101 AxisDirection::Up => (
1102 child_extent - child_bounds.map_or(0.0, |bounds| bounds.top),
1103 trim(
1104 child_bounds,
1105 f64::NEG_INFINITY,
1106 f64::INFINITY,
1107 child_extent,
1108 f64::NEG_INFINITY,
1109 ),
1110 ),
1111 AxisDirection::Right => (
1112 child_bounds.map_or(child_extent, |bounds| bounds.right),
1113 trim(
1114 child_bounds,
1115 f64::NEG_INFINITY,
1116 f64::INFINITY,
1117 f64::INFINITY,
1118 0.0,
1119 ),
1120 ),
1121 AxisDirection::Down => (
1122 child_bounds.map_or(child_extent, |bounds| bounds.bottom),
1123 trim(
1124 child_bounds,
1125 0.0,
1126 f64::INFINITY,
1127 f64::INFINITY,
1128 f64::NEG_INFINITY,
1129 ),
1130 ),
1131 AxisDirection::Left => (
1132 child_extent - child_bounds.map_or(0.0, |bounds| bounds.left),
1133 trim(
1134 child_bounds,
1135 f64::NEG_INFINITY,
1136 child_extent,
1137 f64::INFINITY,
1138 f64::NEG_INFINITY,
1139 ),
1140 ),
1141 };
1142
1143 let max_extent = self.max_extent(app);
1145 let effective_max_extent = child_extent.max(max_extent);
1146
1147 target_extent = clamp_double(
1148 clamp_double(
1149 target_extent,
1150 show_on_screen.min_show_on_screen_extent,
1151 show_on_screen.max_show_on_screen_extent,
1152 ),
1153 child_extent,
1156 effective_max_extent,
1157 );
1158
1159 let controller_status = self
1161 .floating_data(app)
1162 .controller
1163 .map(|controller| controller.status(app));
1164 if target_extent > child_extent && controller_status != Some(AnimationStatus::Forward) {
1165 let target_scroll_offset = max_extent - target_extent;
1166 debug_assert!(
1167 self.vsync(app).is_some(),
1168 "vsync must be set if the floating header changes size animatedly"
1169 );
1170 self.update_animation(app, duration, target_scroll_offset, Rc::clone(&curve));
1171 if let Some(controller) = self.floating_data(app).controller {
1172 controller.forward(app, Some(0.0));
1173 }
1174 }
1175
1176 let descendant = match descendant {
1177 None => Some(self.as_object()),
1178 Some(_) => child.map(AnyRenderBox::as_object),
1179 };
1180 RenderObjectBase::show_on_screen(self, app, descendant, target_rect, duration, curve);
1181 }
1182
1183 fn child_main_axis_position(
1185 self: RenderHandle<Self>,
1186 app: &App,
1187 child: AnyRenderObject,
1188 ) -> f64 {
1189 debug_assert_eq!(Some(child), self.child(app).map(AnyRenderBox::as_object));
1190 self.floating_data(app).child_position.unwrap_or(0.0)
1191 }
1192
1193 fn as_floating_persistent_header(
1196 self: RenderHandle<Self>,
1197 ) -> AnyRenderSliverFloatingPersistentHeader {
1198 AnyRenderSliverFloatingPersistentHeader {
1199 id: self.id(),
1200 vtable: const { &RenderSliverFloatingPersistentHeaderVTable::of::<Self>() },
1201 }
1202 }
1203}
1204
1205struct RenderSliverFloatingPersistentHeaderVTable {
1207 update_scroll_start_direction: fn(&mut App, HandleId, ScrollDirection),
1208 maybe_start_snap_animation: fn(&mut App, HandleId, ScrollDirection),
1209 maybe_stop_snap_animation: fn(&mut App, HandleId, ScrollDirection),
1210}
1211
1212impl RenderSliverFloatingPersistentHeaderVTable {
1213 const fn of<T: RenderSliverFloatingPersistentHeader>()
1214 -> RenderSliverFloatingPersistentHeaderVTable {
1215 RenderSliverFloatingPersistentHeaderVTable {
1216 update_scroll_start_direction: |app, id, direction| {
1217 T::update_scroll_start_direction(resolve(id), app, direction)
1218 },
1219 maybe_start_snap_animation: |app, id, direction| {
1220 T::maybe_start_snap_animation(resolve(id), app, direction)
1221 },
1222 maybe_stop_snap_animation: |app, id, direction| {
1223 T::maybe_stop_snap_animation(resolve(id), app, direction)
1224 },
1225 }
1226 }
1227}
1228
1229#[derive(Clone, Copy)]
1232pub struct AnyRenderSliverFloatingPersistentHeader {
1233 id: HandleId,
1234 vtable: &'static RenderSliverFloatingPersistentHeaderVTable,
1235}
1236
1237impl AnyRenderSliverFloatingPersistentHeader {
1238 pub fn update_scroll_start_direction(self, app: &mut App, direction: ScrollDirection) {
1240 (self.vtable.update_scroll_start_direction)(app, self.id, direction)
1241 }
1242
1243 pub fn maybe_start_snap_animation(self, app: &mut App, direction: ScrollDirection) {
1245 (self.vtable.maybe_start_snap_animation)(app, self.id, direction)
1246 }
1247
1248 pub fn maybe_stop_snap_animation(self, app: &mut App, direction: ScrollDirection) {
1250 (self.vtable.maybe_stop_snap_animation)(app, self.id, direction)
1251 }
1252}
1253
1254impl PartialEq for AnyRenderSliverFloatingPersistentHeader {
1255 fn eq(&self, other: &AnyRenderSliverFloatingPersistentHeader) -> bool {
1256 self.id == other.id
1257 }
1258}
1259
1260impl Eq for AnyRenderSliverFloatingPersistentHeader {}
1261
1262fn snap_tick<T: RenderSliverFloatingPersistentHeader>(this: Handle<T>, app: &mut App) {
1264 let this = RenderHandle::from_handle(this);
1265 let Some(animation) = this.floating_data(app).animation else {
1266 return;
1267 };
1268 let value = animation.value(app);
1269 if this.effective_scroll_offset(app) == Some(value) {
1270 return;
1271 }
1272 this.floating_data_mut(app).effective_scroll_offset = Some(value);
1273 RenderSliverPersistentHeader::mark_needs_layout(this, app);
1274}
1275
1276pub trait RenderSliverFloatingPinnedPersistentHeader: RenderSliverFloatingPersistentHeader {
1285 fn update_geometry(self: RenderHandle<Self>, app: &mut App) -> f64 {
1287 let constraints = self.constraints(app);
1288 let min_extent = self.min_extent(app);
1289 let min_allowed_extent = if constraints.remaining_paint_extent > min_extent {
1290 min_extent
1291 } else {
1292 constraints.remaining_paint_extent
1293 };
1294 let max_extent = self.max_extent(app);
1295 let paint_extent = max_extent
1296 - self
1297 .effective_scroll_offset(app)
1298 .expect("perform_layout sets the effective scroll offset");
1299 let clamped_paint_extent = clamp_double(
1300 paint_extent,
1301 min_allowed_extent,
1302 constraints.remaining_paint_extent,
1303 );
1304 let layout_extent = max_extent - constraints.scroll_offset;
1305 let stretch_offset = if self.stretch_configuration(app).is_some() {
1306 constraints.overlap.abs()
1307 } else {
1308 0.0
1309 };
1310 self.set_geometry(
1311 app,
1312 SliverGeometry::builder()
1313 .scroll_extent(max_extent)
1314 .paint_origin(constraints.overlap.min(0.0))
1315 .paint_extent(clamped_paint_extent)
1316 .layout_extent(clamp_double(layout_extent, 0.0, clamped_paint_extent))
1317 .max_paint_extent(max_extent + stretch_offset)
1318 .max_scroll_obstruction_extent(min_extent)
1319 .has_visual_overflow(true)
1321 .build(),
1322 );
1323 0.0
1324 }
1325}
1326
1327#[cfg(test)]
1328mod tests {
1329 use std::cell::Cell;
1330
1331 use inset_embedder::Size;
1332 use inset_foundation::{AppCell, Task};
1333
1334 use super::*;
1335 use crate::object::{RenderObject, RenderObjectData, RenderObjectWithChildData};
1336 use crate::sliver::{AnyRenderSliver, RenderSliverData};
1337 use crate::sliver_multi_box_adaptor::test_support::hit_testable_box;
1338 use crate::sliver_multi_box_adaptor::viewport_test_support::ScrollHarness;
1339
1340 macro_rules! persistent_header_leaf {
1343 ($leaf:ident, $extra:ident, $perform_layout:path, $child_position:path) => {
1344 struct $leaf {
1345 render_object: RenderObjectData,
1346 render_sliver: RenderSliverData,
1347 child: RenderObjectWithChildData<AnyRenderBox>,
1348 header: RenderSliverPersistentHeaderData,
1349 extra: $extra,
1350 min_extent: f64,
1351 max_extent: f64,
1352 }
1353
1354 impl RenderObjectWithChildMixin for $leaf {
1355 type ChildType = AnyRenderBox;
1356
1357 fn child_data(
1358 self: RenderHandle<Self>,
1359 app: &App,
1360 ) -> &RenderObjectWithChildData<AnyRenderBox> {
1361 &self.get(app).child
1362 }
1363
1364 fn child_data_mut(
1365 self: RenderHandle<Self>,
1366 app: &mut App,
1367 ) -> &mut RenderObjectWithChildData<AnyRenderBox> {
1368 &mut self.get_mut(app).child
1369 }
1370 }
1371
1372 impl RenderSliverHelpers for $leaf {}
1373
1374 impl RenderSliverPersistentHeader for $leaf {
1375 crate::render_sliver_persistent_header_accessors!();
1376
1377 fn max_extent(self: RenderHandle<Self>, app: &App) -> f64 {
1378 self.get(app).max_extent
1379 }
1380
1381 fn min_extent(self: RenderHandle<Self>, app: &App) -> f64 {
1382 self.get(app).min_extent
1383 }
1384 }
1385
1386 impl RenderObject for $leaf {
1387 crate::render_object_accessors!();
1388
1389 fn visit_children(
1390 self: RenderHandle<Self>,
1391 app: &App,
1392 visitor: &mut dyn FnMut(AnyRenderObject),
1393 ) {
1394 if let Some(child) = self.child(app) {
1395 visitor(child.as_object());
1396 }
1397 }
1398
1399 fn apply_paint_transform(
1400 self: RenderHandle<Self>,
1401 app: &App,
1402 child: AnyRenderObject,
1403 transform: &mut Matrix4,
1404 ) {
1405 RenderSliverPersistentHeader::apply_paint_transform(self, app, child, transform)
1406 }
1407
1408 fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
1409 $perform_layout(self, app)
1410 }
1411
1412 fn paint(
1413 self: RenderHandle<Self>,
1414 app: &mut App,
1415 context: &mut PaintingContext,
1416 offset: Offset,
1417 ) {
1418 RenderSliverPersistentHeader::paint(self, app, context, offset)
1419 }
1420 }
1421
1422 impl RenderSliver for $leaf {
1423 crate::render_sliver_accessors!();
1424
1425 fn hit_test_children(
1426 self: RenderHandle<Self>,
1427 app: &mut App,
1428 result: &mut SliverHitTestResult<'_>,
1429 main_axis_position: f64,
1430 cross_axis_position: f64,
1431 ) -> bool {
1432 RenderSliverPersistentHeader::hit_test_children(
1433 self,
1434 app,
1435 result,
1436 main_axis_position,
1437 cross_axis_position,
1438 )
1439 }
1440
1441 fn child_main_axis_position(
1442 self: RenderHandle<Self>,
1443 app: &App,
1444 child: AnyRenderObject,
1445 ) -> f64 {
1446 $child_position(self, app, child)
1447 }
1448 }
1449 };
1450 }
1451
1452 type ScrollingExtra = RenderSliverScrollingPersistentHeaderData;
1453 type PinnedExtra = RenderSliverPinnedPersistentHeaderData;
1454
1455 persistent_header_leaf!(
1456 TestScrollingHeader,
1457 ScrollingExtra,
1458 RenderSliverScrollingPersistentHeader::perform_layout,
1459 RenderSliverScrollingPersistentHeader::child_main_axis_position
1460 );
1461
1462 impl RenderSliverScrollingPersistentHeader for TestScrollingHeader {
1463 fn scrolling_data(
1464 self: RenderHandle<Self>,
1465 app: &App,
1466 ) -> &RenderSliverScrollingPersistentHeaderData {
1467 &self.get(app).extra
1468 }
1469
1470 fn scrolling_data_mut(
1471 self: RenderHandle<Self>,
1472 app: &mut App,
1473 ) -> &mut RenderSliverScrollingPersistentHeaderData {
1474 &mut self.get_mut(app).extra
1475 }
1476 }
1477
1478 persistent_header_leaf!(
1479 TestPinnedHeader,
1480 PinnedExtra,
1481 RenderSliverPinnedPersistentHeader::perform_layout,
1482 RenderSliverPinnedPersistentHeader::child_main_axis_position
1483 );
1484
1485 impl RenderSliverPinnedPersistentHeader for TestPinnedHeader {
1486 fn pinned_data(
1487 self: RenderHandle<Self>,
1488 app: &App,
1489 ) -> &RenderSliverPinnedPersistentHeaderData {
1490 &self.get(app).extra
1491 }
1492
1493 fn pinned_data_mut(
1494 self: RenderHandle<Self>,
1495 app: &mut App,
1496 ) -> &mut RenderSliverPinnedPersistentHeaderData {
1497 &mut self.get_mut(app).extra
1498 }
1499 }
1500
1501 fn scrolling_header(
1502 app: &mut App,
1503 min_extent: f64,
1504 max_extent: f64,
1505 ) -> RenderHandle<TestScrollingHeader> {
1506 let child = hit_testable_box(app, max_extent);
1507 let header = RenderHandle::new_sliver(
1508 app,
1509 TestScrollingHeader {
1510 render_object: RenderObjectData::new(),
1511 render_sliver: RenderSliverData::new(),
1512 child: RenderObjectWithChildData::new(),
1513 header: RenderSliverPersistentHeaderData::new(None),
1514 extra: RenderSliverScrollingPersistentHeaderData::new(),
1515 min_extent,
1516 max_extent,
1517 },
1518 );
1519 header.set_child(app, Some(child));
1520 header
1521 }
1522
1523 fn pinned_header(
1524 app: &mut App,
1525 min_extent: f64,
1526 max_extent: f64,
1527 ) -> RenderHandle<TestPinnedHeader> {
1528 let child = hit_testable_box(app, max_extent);
1529 let header = RenderHandle::new_sliver(
1530 app,
1531 TestPinnedHeader {
1532 render_object: RenderObjectData::new(),
1533 render_sliver: RenderSliverData::new(),
1534 child: RenderObjectWithChildData::new(),
1535 header: RenderSliverPersistentHeaderData::new(None),
1536 extra: RenderSliverPinnedPersistentHeaderData::new(),
1537 min_extent,
1538 max_extent,
1539 },
1540 );
1541 header.set_child(app, Some(child));
1542 header
1543 }
1544
1545 fn lay_out(app: &mut App, sliver: AnyRenderSliver, scroll_offset: f64) -> ScrollHarness {
1548 let harness = ScrollHarness::new(app, sliver, Size::new(200.0, 400.0));
1549 harness.scroll_to(app, scroll_offset);
1550 harness
1551 }
1552
1553 #[test]
1556 fn a_scrolling_header_shrinks_as_it_scrolls_off() {
1557 let cell = AppCell::new();
1558 let mut app = cell.borrow_mut();
1559 let header = scrolling_header(&mut app, 40.0, 120.0);
1560 lay_out(&mut app, header.as_sliver(), 50.0);
1561
1562 let geometry = header.geometry(&app);
1563 assert_eq!(geometry.scroll_extent, 120.0);
1564 assert_eq!(geometry.paint_extent, 70.0);
1565 assert_eq!(geometry.max_scroll_obstruction_extent, 0.0);
1566 assert_eq!(header.child_extent(&app), 70.0);
1568 assert_eq!(header.last_shrink_offset(&app), 50.0);
1569 let child = header.child(&app).expect("has a child");
1570 assert_eq!(
1571 RenderSliver::child_main_axis_position(header, &app, child.as_object()),
1572 0.0
1573 );
1574 }
1575
1576 #[test]
1579 fn a_pinned_header_stays_at_the_leading_edge() {
1580 let cell = AppCell::new();
1581 let mut app = cell.borrow_mut();
1582 let header = pinned_header(&mut app, 40.0, 120.0);
1583 lay_out(&mut app, header.as_sliver(), 100.0);
1584
1585 let geometry = header.geometry(&app);
1586 assert_eq!(geometry.scroll_extent, 120.0);
1587 assert_eq!(geometry.max_scroll_obstruction_extent, 40.0);
1588 assert_eq!(header.child_extent(&app), 40.0);
1590 assert_eq!(geometry.paint_extent, 40.0);
1591 assert_eq!(geometry.layout_extent, 20.0);
1592 let child = header.child(&app).expect("has a child");
1593 assert_eq!(
1594 RenderSliver::child_main_axis_position(header, &app, child.as_object()),
1595 0.0
1596 );
1597 }
1598
1599 #[test]
1601 fn an_unscrolled_pinned_header_is_at_its_maximum_extent() {
1602 let cell = AppCell::new();
1603 let mut app = cell.borrow_mut();
1604 let header = pinned_header(&mut app, 40.0, 120.0);
1605 lay_out(&mut app, header.as_sliver(), 0.0);
1606
1607 let geometry = header.geometry(&app);
1608 assert_eq!(geometry.paint_extent, 120.0);
1609 assert_eq!(geometry.layout_extent, 120.0);
1610 assert_eq!(header.last_shrink_offset(&app), 0.0);
1611 assert!(!header.last_overlaps_content(&app));
1612 }
1613
1614 #[test]
1616 fn layout_child_reports_the_shrink_offset() {
1617 let cell = AppCell::new();
1618 let mut app = cell.borrow_mut();
1619 let header = scrolling_header(&mut app, 40.0, 120.0);
1620 lay_out(&mut app, header.as_sliver(), 80.0);
1621
1622 assert_eq!(header.last_shrink_offset(&app), 80.0);
1623 assert_eq!(header.child_extent(&app), 40.0);
1625 }
1626
1627 #[test]
1630 fn the_stretch_trigger_fires_once_per_crossing() {
1631 let cell = AppCell::new();
1632 let mut app = cell.borrow_mut();
1633 let triggered = Rc::new(Cell::new(0));
1634 let counter = Rc::clone(&triggered);
1635 let configuration =
1636 OverScrollHeaderStretchConfiguration::new().on_stretch_trigger(Rc::new(move |_app| {
1637 counter.set(counter.get() + 1);
1638 Task::ready(())
1639 }));
1640 let header = scrolling_header(&mut app, 40.0, 120.0);
1641 header.set_stretch_configuration(&mut app, Some(configuration));
1642 let harness = lay_out(&mut app, header.as_sliver(), 0.0);
1643 assert_eq!(triggered.get(), 0);
1644
1645 harness.scroll_to(&mut app, -120.0);
1646 assert_eq!(triggered.get(), 1);
1647 harness.scroll_to(&mut app, -130.0);
1648 assert_eq!(triggered.get(), 1, "already past the trigger offset");
1649
1650 harness.scroll_to(&mut app, 0.0);
1651 harness.scroll_to(&mut app, -100.0);
1652 assert_eq!(triggered.get(), 2);
1653 }
1654
1655 #[test]
1657 fn the_show_on_screen_configuration_defaults_to_unbounded() {
1658 let configuration = PersistentHeaderShowOnScreenConfiguration::new();
1659 assert_eq!(configuration.min_show_on_screen_extent, f64::NEG_INFINITY);
1660 assert_eq!(configuration.max_show_on_screen_extent, f64::INFINITY);
1661 let snap = FloatingHeaderSnapConfiguration::new();
1662 assert_eq!(snap.duration, Duration::from_millis(300));
1663 }
1664}