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}