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::new()
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 );
556 if stretch_offset > 0.0 {
557 0.0
558 } else {
559 (paint_extent - self.child_extent(app)).min(0.0)
560 }
561 }
562
563 fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
565 let scroll_offset = self.constraints(app).scroll_offset;
566 let max_extent = self.max_extent(app);
567 self.layout_child(app, scroll_offset, max_extent, false);
568 let child_position = self.update_geometry(app);
569 self.scrolling_data_mut(app).child_position = Some(child_position);
570 }
571
572 fn child_main_axis_position(
574 self: RenderHandle<Self>,
575 app: &App,
576 child: AnyRenderObject,
577 ) -> f64 {
578 debug_assert_eq!(Some(child), self.child(app).map(AnyRenderBox::as_object));
579 self.scrolling_data(app)
580 .child_position
581 .expect("the header has been laid out")
582 }
583}
584
585#[derive(Clone, Copy, Debug)]
587pub struct RenderSliverPinnedPersistentHeaderData {
588 pub show_on_screen_configuration: Option<PersistentHeaderShowOnScreenConfiguration>,
593}
594
595impl RenderSliverPinnedPersistentHeaderData {
596 pub const fn new() -> RenderSliverPinnedPersistentHeaderData {
598 RenderSliverPinnedPersistentHeaderData {
599 show_on_screen_configuration: Some(PersistentHeaderShowOnScreenConfiguration::new()),
600 }
601 }
602}
603
604impl Default for RenderSliverPinnedPersistentHeaderData {
605 fn default() -> RenderSliverPinnedPersistentHeaderData {
606 RenderSliverPinnedPersistentHeaderData::new()
607 }
608}
609
610pub trait RenderSliverPinnedPersistentHeader: RenderSliverPersistentHeader {
616 fn pinned_data(self: RenderHandle<Self>, app: &App) -> &RenderSliverPinnedPersistentHeaderData;
618
619 fn pinned_data_mut(
621 self: RenderHandle<Self>,
622 app: &mut App,
623 ) -> &mut RenderSliverPinnedPersistentHeaderData;
624
625 fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
627 let constraints = self.constraints(app);
628 let max_extent = self.max_extent(app);
629 let overlaps_content = constraints.overlap > 0.0;
630 self.layout_child(app, constraints.scroll_offset, max_extent, overlaps_content);
631 let effective_remaining_paint_extent =
632 (constraints.remaining_paint_extent - constraints.overlap).max(0.0);
633 let layout_extent = clamp_double(
634 max_extent - constraints.scroll_offset,
635 0.0,
636 effective_remaining_paint_extent,
637 );
638 let stretch_offset = if self.stretch_configuration(app).is_some() {
639 constraints.overlap.abs()
640 } else {
641 0.0
642 };
643 let child_extent = self.child_extent(app);
644 let min_extent = self.min_extent(app);
645 self.set_geometry(
646 app,
647 SliverGeometry::new()
648 .scroll_extent(max_extent)
649 .paint_origin(constraints.overlap)
650 .paint_extent(child_extent.min(effective_remaining_paint_extent))
651 .layout_extent(layout_extent)
652 .max_paint_extent(max_extent + stretch_offset)
653 .max_scroll_obstruction_extent(min_extent)
654 .cache_extent(if layout_extent > 0.0 {
655 -constraints.cache_origin + layout_extent
656 } else {
657 layout_extent
658 })
659 .has_visual_overflow(true),
661 );
662 }
663
664 fn child_main_axis_position(
666 self: RenderHandle<Self>,
667 _app: &App,
668 _child: AnyRenderObject,
669 ) -> f64 {
670 let _ = self;
671 0.0
672 }
673
674 fn show_on_screen(
676 self: RenderHandle<Self>,
677 app: &mut App,
678 descendant: Option<AnyRenderObject>,
679 rect: Option<Rect>,
680 duration: Duration,
681 curve: Rc<dyn Curve>,
682 ) {
683 let local_bounds = match descendant {
684 Some(descendant) => Some(transform_rect(
685 &descendant.get_transform_to(app, Some(self.as_object())),
686 rect.unwrap_or_else(|| descendant.paint_bounds(app)),
687 )),
688 None => rect,
689 };
690
691 let constraints = self.constraints(app);
692 let child_extent = self.child_extent(app);
693 let new_rect = match apply_growth_direction_to_axis_direction(
694 constraints.axis_direction,
695 constraints.growth_direction,
696 ) {
697 AxisDirection::Up => trim(
698 local_bounds,
699 f64::NEG_INFINITY,
700 f64::INFINITY,
701 child_extent,
702 f64::NEG_INFINITY,
703 ),
704 AxisDirection::Left => trim(
705 local_bounds,
706 f64::NEG_INFINITY,
707 child_extent,
708 f64::INFINITY,
709 f64::NEG_INFINITY,
710 ),
711 AxisDirection::Right => trim(
712 local_bounds,
713 f64::NEG_INFINITY,
714 f64::INFINITY,
715 f64::INFINITY,
716 0.0,
717 ),
718 AxisDirection::Down => trim(
719 local_bounds,
720 0.0,
721 f64::INFINITY,
722 f64::INFINITY,
723 f64::NEG_INFINITY,
724 ),
725 };
726
727 RenderObjectBase::show_on_screen(
728 self,
729 app,
730 Some(self.as_object()),
731 new_rect,
732 duration,
733 curve,
734 );
735 }
736}
737
738pub struct RenderSliverFloatingPersistentHeaderData {
740 controller: Option<Handle<AnimationController>>,
741 animation: Option<AnyAnimation<f64>>,
742 last_actual_scroll_offset: Option<f64>,
743 effective_scroll_offset: Option<f64>,
744 last_started_scroll_direction: Option<ScrollDirection>,
748 child_position: Option<f64>,
751 vsync: Option<TickerProviderRef>,
752 pub snap_configuration: Option<FloatingHeaderSnapConfiguration>,
756 pub show_on_screen_configuration: Option<PersistentHeaderShowOnScreenConfiguration>,
761}
762
763impl RenderSliverFloatingPersistentHeaderData {
764 pub fn new(
766 vsync: Option<TickerProviderRef>,
767 snap_configuration: Option<FloatingHeaderSnapConfiguration>,
768 show_on_screen_configuration: Option<PersistentHeaderShowOnScreenConfiguration>,
769 ) -> RenderSliverFloatingPersistentHeaderData {
770 RenderSliverFloatingPersistentHeaderData {
771 controller: None,
772 animation: None,
773 last_actual_scroll_offset: None,
774 effective_scroll_offset: None,
775 last_started_scroll_direction: None,
776 child_position: None,
777 vsync,
778 snap_configuration,
779 show_on_screen_configuration,
780 }
781 }
782}
783
784pub trait RenderSliverFloatingPersistentHeader: RenderSliverPersistentHeader {
793 fn floating_data(
795 self: RenderHandle<Self>,
796 app: &App,
797 ) -> &RenderSliverFloatingPersistentHeaderData;
798
799 fn floating_data_mut(
801 self: RenderHandle<Self>,
802 app: &mut App,
803 ) -> &mut RenderSliverFloatingPersistentHeaderData;
804
805 fn did_detach(self: RenderHandle<Self>, app: &mut App) {
808 if let Some(controller) = self.floating_data(app).controller {
809 controller.dispose(app);
810 }
811 self.floating_data_mut(app).controller = None;
812 }
813
814 fn vsync(self: RenderHandle<Self>, app: &App) -> Option<TickerProviderRef> {
816 self.floating_data(app).vsync.clone()
817 }
818
819 fn set_vsync(self: RenderHandle<Self>, app: &mut App, value: Option<TickerProviderRef>) {
821 if value == self.floating_data(app).vsync {
822 return;
823 }
824 self.floating_data_mut(app).vsync = value.clone();
825 match value {
826 None => {
827 if let Some(controller) = self.floating_data(app).controller {
828 controller.dispose(app);
829 }
830 self.floating_data_mut(app).controller = None;
831 }
832 Some(vsync) => {
833 if let Some(controller) = self.floating_data(app).controller {
834 controller.resync(app, vsync);
835 }
836 }
837 }
838 }
839
840 fn snap_configuration(
842 self: RenderHandle<Self>,
843 app: &App,
844 ) -> Option<FloatingHeaderSnapConfiguration> {
845 self.floating_data(app).snap_configuration.clone()
846 }
847
848 fn set_snap_configuration(
850 self: RenderHandle<Self>,
851 app: &mut App,
852 value: Option<FloatingHeaderSnapConfiguration>,
853 ) {
854 self.floating_data_mut(app).snap_configuration = value;
855 }
856
857 fn show_on_screen_configuration(
859 self: RenderHandle<Self>,
860 app: &App,
861 ) -> Option<PersistentHeaderShowOnScreenConfiguration> {
862 self.floating_data(app).show_on_screen_configuration
863 }
864
865 fn set_show_on_screen_configuration(
867 self: RenderHandle<Self>,
868 app: &mut App,
869 value: Option<PersistentHeaderShowOnScreenConfiguration>,
870 ) {
871 self.floating_data_mut(app).show_on_screen_configuration = value;
872 }
873
874 fn effective_scroll_offset(self: RenderHandle<Self>, app: &App) -> Option<f64> {
876 self.floating_data(app).effective_scroll_offset
877 }
878
879 fn update_geometry(self: RenderHandle<Self>, app: &mut App) -> f64 {
882 let constraints = self.constraints(app);
883 let mut stretch_offset = 0.0;
884 if self.stretch_configuration(app).is_some() {
885 stretch_offset += constraints.overlap.abs();
886 }
887 let max_extent = self.max_extent(app);
888 let paint_extent = max_extent
889 - self
890 .effective_scroll_offset(app)
891 .expect("perform_layout sets the effective scroll offset");
892 let layout_extent = max_extent - constraints.scroll_offset;
893 self.set_geometry(
894 app,
895 SliverGeometry::new()
896 .scroll_extent(max_extent)
897 .paint_origin(constraints.overlap.min(0.0))
898 .paint_extent(clamp_double(
899 paint_extent,
900 0.0,
901 constraints.remaining_paint_extent,
902 ))
903 .layout_extent(clamp_double(
904 layout_extent,
905 0.0,
906 constraints.remaining_paint_extent,
907 ))
908 .max_paint_extent(max_extent + stretch_offset)
909 .has_visual_overflow(true),
911 );
912 if stretch_offset > 0.0 {
913 0.0
914 } else {
915 (paint_extent - self.child_extent(app)).min(0.0)
916 }
917 }
918
919 fn update_animation(
921 self: RenderHandle<Self>,
922 app: &mut App,
923 duration: Duration,
924 end_value: f64,
925 curve: Rc<dyn Curve>,
926 ) {
927 let vsync = self
928 .vsync(app)
929 .expect("vsync must be set if the floating header changes size animatedly");
930 let controller = match self.floating_data(app).controller {
931 Some(controller) => controller,
932 None => {
933 let controller = AnimationController::create(
934 app,
935 None,
936 Some(duration),
937 None,
938 0.0,
939 1.0,
940 inset_animation::AnimationBehavior::Normal,
941 vsync,
942 );
943 self.floating_data_mut(app).controller = Some(controller);
944 controller.add_listener(
945 app,
946 Listener::handle_method(self.handle(), snap_tick::<Self>),
947 );
948 controller
949 }
950 };
951 let begin = self.effective_scroll_offset(app);
952 let tween = Tween::new(app, begin, Some(end_value));
953 let curve_tween = CurveTween::new(app, curve);
954 let animation = controller.drive(app, tween.chain(curve_tween));
955 self.floating_data_mut(app).animation = Some(animation);
956 }
957
958 fn update_scroll_start_direction(
960 self: RenderHandle<Self>,
961 app: &mut App,
962 direction: ScrollDirection,
963 ) {
964 self.floating_data_mut(app).last_started_scroll_direction = Some(direction);
965 }
966
967 fn maybe_start_snap_animation(
969 self: RenderHandle<Self>,
970 app: &mut App,
971 direction: ScrollDirection,
972 ) {
973 let Some(snap) = self.snap_configuration(app) else {
974 return;
975 };
976 let effective_scroll_offset = self
977 .effective_scroll_offset(app)
978 .expect("the header has been laid out");
979 let max_extent = self.max_extent(app);
980 if direction == ScrollDirection::Forward && effective_scroll_offset <= 0.0 {
981 return;
982 }
983 if direction == ScrollDirection::Reverse && effective_scroll_offset >= max_extent {
984 return;
985 }
986
987 let end_value = if direction == ScrollDirection::Forward {
988 0.0
989 } else {
990 max_extent
991 };
992 self.update_animation(app, snap.duration, end_value, Rc::clone(&snap.curve));
993 if let Some(controller) = self.floating_data(app).controller {
994 controller.forward(app, Some(0.0));
995 }
996 }
997
998 fn maybe_stop_snap_animation(
1000 self: RenderHandle<Self>,
1001 app: &mut App,
1002 _direction: ScrollDirection,
1003 ) {
1004 if let Some(controller) = self.floating_data(app).controller {
1005 controller.stop(app, false);
1006 }
1007 }
1008
1009 fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
1011 let constraints = self.constraints(app);
1012 let max_extent = self.max_extent(app);
1013 let data = self.floating_data(app);
1014 let last_actual_scroll_offset = data.last_actual_scroll_offset;
1015 let effective_scroll_offset = data.effective_scroll_offset;
1016 let last_started_scroll_direction = data.last_started_scroll_direction;
1017 let new_effective_scroll_offset = match last_actual_scroll_offset {
1021 Some(last_actual_scroll_offset)
1022 if constraints.scroll_offset < last_actual_scroll_offset
1023 || effective_scroll_offset.is_some_and(|offset| offset < max_extent) =>
1024 {
1025 let mut delta = last_actual_scroll_offset - constraints.scroll_offset;
1026 let mut effective_scroll_offset =
1027 effective_scroll_offset.expect("laid out at least once");
1028 let allow_floating_expansion = constraints.user_scroll_direction
1029 == ScrollDirection::Forward
1030 || last_started_scroll_direction == Some(ScrollDirection::Forward);
1031 if allow_floating_expansion {
1032 if effective_scroll_offset > max_extent {
1033 effective_scroll_offset = max_extent;
1036 }
1037 } else if delta > 0.0 {
1038 delta = 0.0;
1040 }
1041 clamp_double(
1042 effective_scroll_offset - delta,
1043 0.0,
1044 constraints.scroll_offset,
1045 )
1046 }
1047 _ => constraints.scroll_offset,
1048 };
1049 self.floating_data_mut(app).effective_scroll_offset = Some(new_effective_scroll_offset);
1050 let overlaps_content = new_effective_scroll_offset < constraints.scroll_offset;
1051
1052 self.layout_child(
1053 app,
1054 new_effective_scroll_offset,
1055 max_extent,
1056 overlaps_content,
1057 );
1058 let child_position = self.update_geometry(app);
1059 let data = self.floating_data_mut(app);
1060 data.child_position = Some(child_position);
1061 data.last_actual_scroll_offset = Some(constraints.scroll_offset);
1062 }
1063
1064 fn show_on_screen(
1066 self: RenderHandle<Self>,
1067 app: &mut App,
1068 descendant: Option<AnyRenderObject>,
1069 rect: Option<Rect>,
1070 duration: Duration,
1071 curve: Rc<dyn Curve>,
1072 ) {
1073 let Some(show_on_screen) = self.show_on_screen_configuration(app) else {
1074 return RenderObjectBase::show_on_screen(self, app, descendant, rect, duration, curve);
1075 };
1076
1077 let child = self.child(app);
1078 debug_assert!(child.is_some() || descendant.is_none());
1079 let child_bounds = match descendant {
1085 Some(descendant) => Some(transform_rect(
1086 &descendant.get_transform_to(app, child.map(AnyRenderBox::as_object)),
1087 rect.unwrap_or_else(|| descendant.paint_bounds(app)),
1088 )),
1089 None => rect,
1090 };
1091
1092 let constraints = self.constraints(app);
1093 let child_extent = self.child_extent(app);
1094 let (mut target_extent, target_rect) = match apply_growth_direction_to_axis_direction(
1095 constraints.axis_direction,
1096 constraints.growth_direction,
1097 ) {
1098 AxisDirection::Up => (
1099 child_extent - child_bounds.map_or(0.0, |bounds| bounds.top),
1100 trim(
1101 child_bounds,
1102 f64::NEG_INFINITY,
1103 f64::INFINITY,
1104 child_extent,
1105 f64::NEG_INFINITY,
1106 ),
1107 ),
1108 AxisDirection::Right => (
1109 child_bounds.map_or(child_extent, |bounds| bounds.right),
1110 trim(
1111 child_bounds,
1112 f64::NEG_INFINITY,
1113 f64::INFINITY,
1114 f64::INFINITY,
1115 0.0,
1116 ),
1117 ),
1118 AxisDirection::Down => (
1119 child_bounds.map_or(child_extent, |bounds| bounds.bottom),
1120 trim(
1121 child_bounds,
1122 0.0,
1123 f64::INFINITY,
1124 f64::INFINITY,
1125 f64::NEG_INFINITY,
1126 ),
1127 ),
1128 AxisDirection::Left => (
1129 child_extent - child_bounds.map_or(0.0, |bounds| bounds.left),
1130 trim(
1131 child_bounds,
1132 f64::NEG_INFINITY,
1133 child_extent,
1134 f64::INFINITY,
1135 f64::NEG_INFINITY,
1136 ),
1137 ),
1138 };
1139
1140 let max_extent = self.max_extent(app);
1142 let effective_max_extent = child_extent.max(max_extent);
1143
1144 target_extent = clamp_double(
1145 clamp_double(
1146 target_extent,
1147 show_on_screen.min_show_on_screen_extent,
1148 show_on_screen.max_show_on_screen_extent,
1149 ),
1150 child_extent,
1153 effective_max_extent,
1154 );
1155
1156 let controller_status = self
1158 .floating_data(app)
1159 .controller
1160 .map(|controller| controller.status(app));
1161 if target_extent > child_extent && controller_status != Some(AnimationStatus::Forward) {
1162 let target_scroll_offset = max_extent - target_extent;
1163 debug_assert!(
1164 self.vsync(app).is_some(),
1165 "vsync must be set if the floating header changes size animatedly"
1166 );
1167 self.update_animation(app, duration, target_scroll_offset, Rc::clone(&curve));
1168 if let Some(controller) = self.floating_data(app).controller {
1169 controller.forward(app, Some(0.0));
1170 }
1171 }
1172
1173 let descendant = match descendant {
1174 None => Some(self.as_object()),
1175 Some(_) => child.map(AnyRenderBox::as_object),
1176 };
1177 RenderObjectBase::show_on_screen(self, app, descendant, target_rect, duration, curve);
1178 }
1179
1180 fn child_main_axis_position(
1182 self: RenderHandle<Self>,
1183 app: &App,
1184 child: AnyRenderObject,
1185 ) -> f64 {
1186 debug_assert_eq!(Some(child), self.child(app).map(AnyRenderBox::as_object));
1187 self.floating_data(app).child_position.unwrap_or(0.0)
1188 }
1189
1190 fn as_floating_persistent_header(
1193 self: RenderHandle<Self>,
1194 ) -> AnyRenderSliverFloatingPersistentHeader {
1195 AnyRenderSliverFloatingPersistentHeader {
1196 id: self.id(),
1197 vtable: const { &RenderSliverFloatingPersistentHeaderVTable::of::<Self>() },
1198 }
1199 }
1200}
1201
1202struct RenderSliverFloatingPersistentHeaderVTable {
1204 update_scroll_start_direction: fn(&mut App, HandleId, ScrollDirection),
1205 maybe_start_snap_animation: fn(&mut App, HandleId, ScrollDirection),
1206 maybe_stop_snap_animation: fn(&mut App, HandleId, ScrollDirection),
1207}
1208
1209impl RenderSliverFloatingPersistentHeaderVTable {
1210 const fn of<T: RenderSliverFloatingPersistentHeader>()
1211 -> RenderSliverFloatingPersistentHeaderVTable {
1212 RenderSliverFloatingPersistentHeaderVTable {
1213 update_scroll_start_direction: |app, id, direction| {
1214 T::update_scroll_start_direction(resolve(id), app, direction)
1215 },
1216 maybe_start_snap_animation: |app, id, direction| {
1217 T::maybe_start_snap_animation(resolve(id), app, direction)
1218 },
1219 maybe_stop_snap_animation: |app, id, direction| {
1220 T::maybe_stop_snap_animation(resolve(id), app, direction)
1221 },
1222 }
1223 }
1224}
1225
1226#[derive(Clone, Copy)]
1229pub struct AnyRenderSliverFloatingPersistentHeader {
1230 id: HandleId,
1231 vtable: &'static RenderSliverFloatingPersistentHeaderVTable,
1232}
1233
1234impl AnyRenderSliverFloatingPersistentHeader {
1235 pub fn update_scroll_start_direction(self, app: &mut App, direction: ScrollDirection) {
1237 (self.vtable.update_scroll_start_direction)(app, self.id, direction)
1238 }
1239
1240 pub fn maybe_start_snap_animation(self, app: &mut App, direction: ScrollDirection) {
1242 (self.vtable.maybe_start_snap_animation)(app, self.id, direction)
1243 }
1244
1245 pub fn maybe_stop_snap_animation(self, app: &mut App, direction: ScrollDirection) {
1247 (self.vtable.maybe_stop_snap_animation)(app, self.id, direction)
1248 }
1249}
1250
1251impl PartialEq for AnyRenderSliverFloatingPersistentHeader {
1252 fn eq(&self, other: &AnyRenderSliverFloatingPersistentHeader) -> bool {
1253 self.id == other.id
1254 }
1255}
1256
1257impl Eq for AnyRenderSliverFloatingPersistentHeader {}
1258
1259fn snap_tick<T: RenderSliverFloatingPersistentHeader>(this: Handle<T>, app: &mut App) {
1261 let this = RenderHandle::from_handle(this);
1262 let Some(animation) = this.floating_data(app).animation else {
1263 return;
1264 };
1265 let value = animation.value(app);
1266 if this.effective_scroll_offset(app) == Some(value) {
1267 return;
1268 }
1269 this.floating_data_mut(app).effective_scroll_offset = Some(value);
1270 RenderSliverPersistentHeader::mark_needs_layout(this, app);
1271}
1272
1273pub trait RenderSliverFloatingPinnedPersistentHeader: RenderSliverFloatingPersistentHeader {
1282 fn update_geometry(self: RenderHandle<Self>, app: &mut App) -> f64 {
1284 let constraints = self.constraints(app);
1285 let min_extent = self.min_extent(app);
1286 let min_allowed_extent = if constraints.remaining_paint_extent > min_extent {
1287 min_extent
1288 } else {
1289 constraints.remaining_paint_extent
1290 };
1291 let max_extent = self.max_extent(app);
1292 let paint_extent = max_extent
1293 - self
1294 .effective_scroll_offset(app)
1295 .expect("perform_layout sets the effective scroll offset");
1296 let clamped_paint_extent = clamp_double(
1297 paint_extent,
1298 min_allowed_extent,
1299 constraints.remaining_paint_extent,
1300 );
1301 let layout_extent = max_extent - constraints.scroll_offset;
1302 let stretch_offset = if self.stretch_configuration(app).is_some() {
1303 constraints.overlap.abs()
1304 } else {
1305 0.0
1306 };
1307 self.set_geometry(
1308 app,
1309 SliverGeometry::new()
1310 .scroll_extent(max_extent)
1311 .paint_origin(constraints.overlap.min(0.0))
1312 .paint_extent(clamped_paint_extent)
1313 .layout_extent(clamp_double(layout_extent, 0.0, clamped_paint_extent))
1314 .max_paint_extent(max_extent + stretch_offset)
1315 .max_scroll_obstruction_extent(min_extent)
1316 .has_visual_overflow(true),
1318 );
1319 0.0
1320 }
1321}
1322
1323#[cfg(test)]
1324mod tests {
1325 use std::cell::Cell;
1326
1327 use inset_embedder::Size;
1328 use inset_foundation::{AppCell, Task};
1329
1330 use super::*;
1331 use crate::object::{RenderObject, RenderObjectData, RenderObjectWithChildData};
1332 use crate::sliver::{AnyRenderSliver, RenderSliverData};
1333 use crate::sliver_multi_box_adaptor::test_support::hit_testable_box;
1334 use crate::sliver_multi_box_adaptor::viewport_test_support::ScrollHarness;
1335
1336 macro_rules! persistent_header_leaf {
1339 ($leaf:ident, $extra:ident, $perform_layout:path, $child_position:path) => {
1340 struct $leaf {
1341 render_object: RenderObjectData,
1342 render_sliver: RenderSliverData,
1343 child: RenderObjectWithChildData<AnyRenderBox>,
1344 header: RenderSliverPersistentHeaderData,
1345 extra: $extra,
1346 min_extent: f64,
1347 max_extent: f64,
1348 }
1349
1350 impl RenderObjectWithChildMixin for $leaf {
1351 type ChildType = AnyRenderBox;
1352
1353 fn child_data(
1354 self: RenderHandle<Self>,
1355 app: &App,
1356 ) -> &RenderObjectWithChildData<AnyRenderBox> {
1357 &self.get(app).child
1358 }
1359
1360 fn child_data_mut(
1361 self: RenderHandle<Self>,
1362 app: &mut App,
1363 ) -> &mut RenderObjectWithChildData<AnyRenderBox> {
1364 &mut self.get_mut(app).child
1365 }
1366 }
1367
1368 impl RenderSliverHelpers for $leaf {}
1369
1370 impl RenderSliverPersistentHeader for $leaf {
1371 crate::render_sliver_persistent_header_accessors!();
1372
1373 fn max_extent(self: RenderHandle<Self>, app: &App) -> f64 {
1374 self.get(app).max_extent
1375 }
1376
1377 fn min_extent(self: RenderHandle<Self>, app: &App) -> f64 {
1378 self.get(app).min_extent
1379 }
1380 }
1381
1382 impl RenderObject for $leaf {
1383 crate::render_object_accessors!();
1384
1385 fn visit_children(
1386 self: RenderHandle<Self>,
1387 app: &App,
1388 visitor: &mut dyn FnMut(AnyRenderObject),
1389 ) {
1390 if let Some(child) = self.child(app) {
1391 visitor(child.as_object());
1392 }
1393 }
1394
1395 fn apply_paint_transform(
1396 self: RenderHandle<Self>,
1397 app: &App,
1398 child: AnyRenderObject,
1399 transform: &mut Matrix4,
1400 ) {
1401 RenderSliverPersistentHeader::apply_paint_transform(self, app, child, transform)
1402 }
1403
1404 fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
1405 $perform_layout(self, app)
1406 }
1407
1408 fn paint(
1409 self: RenderHandle<Self>,
1410 app: &mut App,
1411 context: &mut PaintingContext,
1412 offset: Offset,
1413 ) {
1414 RenderSliverPersistentHeader::paint(self, app, context, offset)
1415 }
1416 }
1417
1418 impl RenderSliver for $leaf {
1419 crate::render_sliver_accessors!();
1420
1421 fn hit_test_children(
1422 self: RenderHandle<Self>,
1423 app: &mut App,
1424 result: &mut SliverHitTestResult<'_>,
1425 main_axis_position: f64,
1426 cross_axis_position: f64,
1427 ) -> bool {
1428 RenderSliverPersistentHeader::hit_test_children(
1429 self,
1430 app,
1431 result,
1432 main_axis_position,
1433 cross_axis_position,
1434 )
1435 }
1436
1437 fn child_main_axis_position(
1438 self: RenderHandle<Self>,
1439 app: &App,
1440 child: AnyRenderObject,
1441 ) -> f64 {
1442 $child_position(self, app, child)
1443 }
1444 }
1445 };
1446 }
1447
1448 type ScrollingExtra = RenderSliverScrollingPersistentHeaderData;
1449 type PinnedExtra = RenderSliverPinnedPersistentHeaderData;
1450
1451 persistent_header_leaf!(
1452 TestScrollingHeader,
1453 ScrollingExtra,
1454 RenderSliverScrollingPersistentHeader::perform_layout,
1455 RenderSliverScrollingPersistentHeader::child_main_axis_position
1456 );
1457
1458 impl RenderSliverScrollingPersistentHeader for TestScrollingHeader {
1459 fn scrolling_data(
1460 self: RenderHandle<Self>,
1461 app: &App,
1462 ) -> &RenderSliverScrollingPersistentHeaderData {
1463 &self.get(app).extra
1464 }
1465
1466 fn scrolling_data_mut(
1467 self: RenderHandle<Self>,
1468 app: &mut App,
1469 ) -> &mut RenderSliverScrollingPersistentHeaderData {
1470 &mut self.get_mut(app).extra
1471 }
1472 }
1473
1474 persistent_header_leaf!(
1475 TestPinnedHeader,
1476 PinnedExtra,
1477 RenderSliverPinnedPersistentHeader::perform_layout,
1478 RenderSliverPinnedPersistentHeader::child_main_axis_position
1479 );
1480
1481 impl RenderSliverPinnedPersistentHeader for TestPinnedHeader {
1482 fn pinned_data(
1483 self: RenderHandle<Self>,
1484 app: &App,
1485 ) -> &RenderSliverPinnedPersistentHeaderData {
1486 &self.get(app).extra
1487 }
1488
1489 fn pinned_data_mut(
1490 self: RenderHandle<Self>,
1491 app: &mut App,
1492 ) -> &mut RenderSliverPinnedPersistentHeaderData {
1493 &mut self.get_mut(app).extra
1494 }
1495 }
1496
1497 fn scrolling_header(
1498 app: &mut App,
1499 min_extent: f64,
1500 max_extent: f64,
1501 ) -> RenderHandle<TestScrollingHeader> {
1502 let child = hit_testable_box(app, max_extent);
1503 let header = RenderHandle::new_sliver(
1504 app,
1505 TestScrollingHeader {
1506 render_object: RenderObjectData::new(),
1507 render_sliver: RenderSliverData::new(),
1508 child: RenderObjectWithChildData::new(),
1509 header: RenderSliverPersistentHeaderData::new(None),
1510 extra: RenderSliverScrollingPersistentHeaderData::new(),
1511 min_extent,
1512 max_extent,
1513 },
1514 );
1515 header.set_child(app, Some(child));
1516 header
1517 }
1518
1519 fn pinned_header(
1520 app: &mut App,
1521 min_extent: f64,
1522 max_extent: f64,
1523 ) -> RenderHandle<TestPinnedHeader> {
1524 let child = hit_testable_box(app, max_extent);
1525 let header = RenderHandle::new_sliver(
1526 app,
1527 TestPinnedHeader {
1528 render_object: RenderObjectData::new(),
1529 render_sliver: RenderSliverData::new(),
1530 child: RenderObjectWithChildData::new(),
1531 header: RenderSliverPersistentHeaderData::new(None),
1532 extra: RenderSliverPinnedPersistentHeaderData::new(),
1533 min_extent,
1534 max_extent,
1535 },
1536 );
1537 header.set_child(app, Some(child));
1538 header
1539 }
1540
1541 fn lay_out(app: &mut App, sliver: AnyRenderSliver, scroll_offset: f64) -> ScrollHarness {
1544 let harness = ScrollHarness::new(app, sliver, Size::new(200.0, 400.0));
1545 harness.scroll_to(app, scroll_offset);
1546 harness
1547 }
1548
1549 #[test]
1552 fn a_scrolling_header_shrinks_as_it_scrolls_off() {
1553 let cell = AppCell::new();
1554 let mut app = cell.borrow_mut();
1555 let header = scrolling_header(&mut app, 40.0, 120.0);
1556 lay_out(&mut app, header.as_sliver(), 50.0);
1557
1558 let geometry = header.geometry(&app);
1559 assert_eq!(geometry.scroll_extent, 120.0);
1560 assert_eq!(geometry.paint_extent, 70.0);
1561 assert_eq!(geometry.max_scroll_obstruction_extent, 0.0);
1562 assert_eq!(header.child_extent(&app), 70.0);
1564 assert_eq!(header.last_shrink_offset(&app), 50.0);
1565 let child = header.child(&app).expect("has a child");
1566 assert_eq!(
1567 RenderSliver::child_main_axis_position(header, &app, child.as_object()),
1568 0.0
1569 );
1570 }
1571
1572 #[test]
1575 fn a_pinned_header_stays_at_the_leading_edge() {
1576 let cell = AppCell::new();
1577 let mut app = cell.borrow_mut();
1578 let header = pinned_header(&mut app, 40.0, 120.0);
1579 lay_out(&mut app, header.as_sliver(), 100.0);
1580
1581 let geometry = header.geometry(&app);
1582 assert_eq!(geometry.scroll_extent, 120.0);
1583 assert_eq!(geometry.max_scroll_obstruction_extent, 40.0);
1584 assert_eq!(header.child_extent(&app), 40.0);
1586 assert_eq!(geometry.paint_extent, 40.0);
1587 assert_eq!(geometry.layout_extent, 20.0);
1588 let child = header.child(&app).expect("has a child");
1589 assert_eq!(
1590 RenderSliver::child_main_axis_position(header, &app, child.as_object()),
1591 0.0
1592 );
1593 }
1594
1595 #[test]
1597 fn an_unscrolled_pinned_header_is_at_its_maximum_extent() {
1598 let cell = AppCell::new();
1599 let mut app = cell.borrow_mut();
1600 let header = pinned_header(&mut app, 40.0, 120.0);
1601 lay_out(&mut app, header.as_sliver(), 0.0);
1602
1603 let geometry = header.geometry(&app);
1604 assert_eq!(geometry.paint_extent, 120.0);
1605 assert_eq!(geometry.layout_extent, 120.0);
1606 assert_eq!(header.last_shrink_offset(&app), 0.0);
1607 assert!(!header.last_overlaps_content(&app));
1608 }
1609
1610 #[test]
1612 fn layout_child_reports_the_shrink_offset() {
1613 let cell = AppCell::new();
1614 let mut app = cell.borrow_mut();
1615 let header = scrolling_header(&mut app, 40.0, 120.0);
1616 lay_out(&mut app, header.as_sliver(), 80.0);
1617
1618 assert_eq!(header.last_shrink_offset(&app), 80.0);
1619 assert_eq!(header.child_extent(&app), 40.0);
1621 }
1622
1623 #[test]
1626 fn the_stretch_trigger_fires_once_per_crossing() {
1627 let cell = AppCell::new();
1628 let mut app = cell.borrow_mut();
1629 let triggered = Rc::new(Cell::new(0));
1630 let counter = Rc::clone(&triggered);
1631 let configuration =
1632 OverScrollHeaderStretchConfiguration::new().on_stretch_trigger(Rc::new(move |_app| {
1633 counter.set(counter.get() + 1);
1634 Task::ready(())
1635 }));
1636 let header = scrolling_header(&mut app, 40.0, 120.0);
1637 header.set_stretch_configuration(&mut app, Some(configuration));
1638 let harness = lay_out(&mut app, header.as_sliver(), 0.0);
1639 assert_eq!(triggered.get(), 0);
1640
1641 harness.scroll_to(&mut app, -120.0);
1642 assert_eq!(triggered.get(), 1);
1643 harness.scroll_to(&mut app, -130.0);
1644 assert_eq!(triggered.get(), 1, "already past the trigger offset");
1645
1646 harness.scroll_to(&mut app, 0.0);
1647 harness.scroll_to(&mut app, -100.0);
1648 assert_eq!(triggered.get(), 2);
1649 }
1650
1651 #[test]
1653 fn the_show_on_screen_configuration_defaults_to_unbounded() {
1654 let configuration = PersistentHeaderShowOnScreenConfiguration::new();
1655 assert_eq!(configuration.min_show_on_screen_extent, f64::NEG_INFINITY);
1656 assert_eq!(configuration.max_show_on_screen_extent, f64::INFINITY);
1657 let snap = FloatingHeaderSnapConfiguration::new();
1658 assert_eq!(snap.duration, Duration::from_millis(300));
1659 }
1660}