Skip to main content

animato_tween/
builder.rs

1//! Builder for [`Tween<T>`].
2
3use crate::loop_mode::Loop;
4use crate::tween::Tween;
5use animato_core::{Animatable, Easing};
6
7/// Consuming builder for [`Tween<T>`].
8///
9/// Obtained via [`Tween::new`]. All fields have sensible defaults —
10/// only `start` and `end` are required.
11///
12/// # Example
13///
14/// ```rust
15/// use animato_tween::Tween;
16/// use animato_core::Easing;
17///
18/// let tween = Tween::new(0.0_f32, 100.0)
19///     .duration(1.5)
20///     .easing(Easing::EaseOutBack)
21///     .delay(0.1)
22///     .time_scale(1.0)
23///     .build();
24/// ```
25pub struct TweenBuilder<T: Animatable> {
26    start: T,
27    end: T,
28    duration: f32,
29    easing: Easing,
30    delay: f32,
31    time_scale: f32,
32    looping: Loop,
33}
34
35impl<T: Animatable + core::fmt::Debug> core::fmt::Debug for TweenBuilder<T> {
36    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
37        f.debug_struct("TweenBuilder")
38            .field("start", &self.start)
39            .field("end", &self.end)
40            .field("duration", &self.duration)
41            .field("easing", &self.easing)
42            .field("delay", &self.delay)
43            .field("time_scale", &self.time_scale)
44            .field("looping", &self.looping)
45            .finish()
46    }
47}
48
49impl<T: Animatable> TweenBuilder<T> {
50    /// Create a builder animating from `start` to `end`.
51    #[inline]
52    pub fn new(start: T, end: T) -> Self {
53        Self {
54            start,
55            end,
56            duration: 1.0,
57            easing: Easing::Linear,
58            delay: 0.0,
59            time_scale: 1.0,
60            looping: Loop::Once,
61        }
62    }
63
64    /// Set the animation duration in seconds. Negative values are clamped to `0`.
65    #[inline]
66    pub fn duration(mut self, secs: f32) -> Self {
67        self.duration = secs.max(0.0);
68        self
69    }
70
71    /// Set the easing curve.
72    #[inline]
73    pub fn easing(mut self, e: Easing) -> Self {
74        self.easing = e;
75        self
76    }
77
78    /// Set a pre-animation delay in seconds. The value is held at `start` during this period.
79    #[inline]
80    pub fn delay(mut self, secs: f32) -> Self {
81        self.delay = secs.max(0.0);
82        self
83    }
84
85    /// Set the time scale multiplier (`1.0` = normal speed, `2.0` = double speed).
86    #[inline]
87    pub fn time_scale(mut self, s: f32) -> Self {
88        self.time_scale = s.max(0.0);
89        self
90    }
91
92    /// Set the looping mode.
93    #[inline]
94    pub fn looping(mut self, mode: Loop) -> Self {
95        self.looping = mode;
96        self
97    }
98
99    /// Consume the builder and return the configured [`Tween<T>`].
100    #[inline]
101    pub fn build(self) -> Tween<T> {
102        Tween::from_builder(
103            self.start,
104            self.end,
105            self.duration,
106            self.easing,
107            self.delay,
108            self.time_scale,
109            self.looping,
110        )
111    }
112}
113
114impl<T: Animatable> Tween<T> {
115    /// Begin building a tween from `start` to `end`.
116    ///
117    /// ```rust
118    /// use animato_tween::Tween;
119    /// use animato_core::Easing;
120    ///
121    /// let t = Tween::new(0.0_f32, 1.0)
122    ///     .duration(2.0)
123    ///     .easing(Easing::EaseInOutSine)
124    ///     .build();
125    /// ```
126    #[allow(clippy::new_ret_no_self)]
127    pub fn new(start: T, end: T) -> TweenBuilder<T> {
128        TweenBuilder::new(start, end)
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135    use animato_core::Update;
136
137    #[test]
138    fn builder_defaults() {
139        let t = Tween::new(0.0_f32, 1.0).build();
140        assert_eq!(t.duration, 1.0);
141        assert_eq!(t.delay, 0.0);
142        assert_eq!(t.time_scale, 1.0);
143        assert_eq!(t.looping, Loop::Once);
144    }
145
146    #[test]
147    fn builder_chains() {
148        let mut t = Tween::new(0.0_f32, 100.0)
149            .duration(2.0)
150            .delay(0.5)
151            .time_scale(2.0)
152            .looping(Loop::Forever)
153            .build();
154        assert_eq!(t.duration, 2.0);
155        // time_scale=2.0: 0.5s of dt advances 1.0s of animation time
156        // delay=0.5, so after 0.5s of dt → running starts
157        t.update(0.5); // exhausts delay
158        t.update(0.5); // 0.5 * time_scale(2) = 1.0s of anim time
159        assert!(t.value() > 0.0);
160    }
161
162    #[test]
163    fn negative_duration_clamped() {
164        let t = Tween::new(0.0_f32, 1.0).duration(-5.0).build();
165        assert_eq!(t.duration, 0.0);
166    }
167
168    #[test]
169    fn negative_delay_clamped() {
170        let t = Tween::new(0.0_f32, 1.0).delay(-1.0).build();
171        assert_eq!(t.delay, 0.0);
172    }
173}