Skip to main content

sequoia_view/
scrub.rs

1#![forbid(unsafe_code)]
2
3use sequoia_core::Vec2;
4
5use crate::any::AnyView;
6use crate::live::Live;
7use crate::runtime::{ElementId, Runtime};
8use crate::view::View;
9use sequoia_animation::Animate;
10
11#[derive(Clone)]
12pub struct Scrub {
13    child: AnyView,
14    progress: Live<f32>,
15    anim: Animate,
16}
17
18impl Scrub {
19    pub fn new<V: View + 'static>(child: V, progress: impl Into<Live<f32>>, anim: Animate) -> Self {
20        Self {
21            child: AnyView::new(child),
22            progress: progress.into(),
23            anim,
24        }
25    }
26
27    pub fn progress_untracked(&self) -> f32 {
28        self.progress.target_untracked().clamp(0.0, 1.0)
29    }
30
31    fn frame(&self) -> (Option<f32>, Option<Vec2>) {
32        let p = self.progress_untracked();
33        let total = (self.anim.duration_secs + self.anim.delay_secs).max(0.0);
34        let f = self.anim.sample_at(p * total);
35        (f.opacity, f.offset)
36    }
37}
38
39impl View for Scrub {
40    fn body(&self) -> impl View + 'static {
41        crate::views::Unit
42    }
43
44    fn children_override(&self) -> Option<Vec<AnyView>> {
45        let _ = self.progress.target();
46        Some(vec![self.child.clone()])
47    }
48
49    fn paint_opacity(&self) -> Option<f32> {
50        self.frame().0
51    }
52
53    fn paint_offset(&self) -> Vec2 {
54        self.frame().1.unwrap_or(Vec2::ZERO)
55    }
56
57    fn paint_opacity_ctx(&self, _rt: &Runtime, _el: ElementId) -> Option<f32> {
58        self.frame().0
59    }
60
61    fn paint_offset_ctx(&self, _rt: &Runtime, _el: ElementId) -> Vec2 {
62        self.frame().1.unwrap_or(Vec2::ZERO)
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69    use crate::live::SignalLiveExt;
70    use sequoia_reactive::Signal;
71
72    fn anim() -> Animate {
73        sequoia_animation::animate()
74            .opacity(0.0..1.0)
75            .translate_y(-8.0..0.0)
76            .build()
77    }
78
79    #[test]
80    fn scrub_maps_progress_to_frame() {
81        let p = Signal::new(0.0f32);
82        let s = Scrub::new(crate::views::Unit, p.clone().live(), anim());
83        assert_eq!(s.paint_opacity(), Some(0.0));
84        assert_eq!(s.paint_offset(), Vec2::new(0.0, -8.0));
85        p.set(1.0);
86        assert_eq!(s.paint_opacity(), Some(1.0));
87        assert_eq!(s.paint_offset(), Vec2::ZERO);
88    }
89
90    #[test]
91    fn scrub_clamps_progress() {
92        let s = Scrub::new(crate::views::Unit, Live::fixed(-5.0f32), anim());
93        assert_eq!(s.paint_opacity(), Some(0.0));
94        let s = Scrub::new(crate::views::Unit, Live::fixed(99.0f32), anim());
95        assert_eq!(s.paint_opacity(), Some(1.0));
96    }
97
98    #[test]
99    fn scrub_tracks_signal() {
100        let p = Signal::new(0.25f32);
101        let s = Scrub::new(crate::views::Unit, p.clone().live(), anim());
102        let kids = s.children_override().expect("dziecko");
103        assert_eq!(kids.len(), 1);
104        p.set(0.75);
105        assert!((s.progress_untracked() - 0.75).abs() < 1e-6);
106    }
107}