1use crate::{AnimationTarget, AnimationValue, Easing, Transition};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone)]
8pub struct AnimationBuilder {
9 initial: Option<AnimationTarget>,
10 animate: Option<AnimationTarget>,
11 exit: Option<AnimationTarget>,
12 transition: Option<Transition>,
13 variants: Option<Variants>,
14}
15
16impl AnimationBuilder {
17 pub fn new() -> Self {
19 Self {
20 initial: None,
21 animate: None,
22 exit: None,
23 transition: None,
24 variants: None,
25 }
26 }
27
28 pub fn initial(mut self, initial: AnimationTarget) -> Self {
30 self.initial = Some(initial);
31 self
32 }
33
34 pub fn animate(mut self, animate: AnimationTarget) -> Self {
36 self.animate = Some(animate);
37 self
38 }
39
40 pub fn exit(mut self, exit: AnimationTarget) -> Self {
42 self.exit = Some(exit);
43 self
44 }
45
46 pub fn transition(mut self, transition: Transition) -> Self {
48 self.transition = Some(transition);
49 self
50 }
51
52 pub fn variants(mut self, variants: Variants) -> Self {
54 self.variants = Some(variants);
55 self
56 }
57
58 pub fn build(self) -> AnimationConfig {
60 AnimationConfig {
61 initial: self.initial.unwrap_or_default(),
62 animate: self.animate.unwrap_or_default(),
63 exit: self.exit.unwrap_or_default(),
64 transition: self.transition.unwrap_or_default(),
65 variants: self.variants,
66 }
67 }
68}
69
70#[derive(Debug, Clone, Default)]
72pub struct AnimationConfig {
73 pub initial: AnimationTarget,
75 pub animate: AnimationTarget,
77 pub exit: AnimationTarget,
79 pub transition: Transition,
81 pub variants: Option<Variants>,
83}
84
85#[derive(Debug, Clone)]
87pub struct Variants {
88 pub variants: HashMap<String, AnimationTarget>,
90 pub initial: Option<String>,
92 pub animate: Option<String>,
94 pub exit: Option<String>,
96 pub hover: Option<String>,
98 pub tap: Option<String>,
100 pub focus: Option<String>,
102 pub drag: Option<String>,
104}
105
106impl Variants {
107 pub fn new() -> Self {
109 Self {
110 variants: HashMap::new(),
111 initial: None,
112 animate: None,
113 exit: None,
114 hover: None,
115 tap: None,
116 focus: None,
117 drag: None,
118 }
119 }
120
121 pub fn add_variant(mut self, name: impl Into<String>, target: AnimationTarget) -> Self {
123 self.variants.insert(name.into(), target);
124 self
125 }
126
127 pub fn initial(mut self, name: impl Into<String>) -> Self {
129 self.initial = Some(name.into());
130 self
131 }
132
133 pub fn animate(mut self, name: impl Into<String>) -> Self {
135 self.animate = Some(name.into());
136 self
137 }
138
139 pub fn exit(mut self, name: impl Into<String>) -> Self {
141 self.exit = Some(name.into());
142 self
143 }
144
145 pub fn hover(mut self, name: impl Into<String>) -> Self {
147 self.hover = Some(name.into());
148 self
149 }
150
151 pub fn tap(mut self, name: impl Into<String>) -> Self {
153 self.tap = Some(name.into());
154 self
155 }
156
157 pub fn get_variant(&self, name: &str) -> Option<&AnimationTarget> {
159 self.variants.get(name)
160 }
161}
162
163#[derive(Debug, Clone)]
165pub struct Keyframes {
166 pub times: Vec<f64>,
168 pub values: Vec<AnimationTarget>,
170 pub easings: Vec<Easing>,
172}
173
174impl Keyframes {
175 pub fn new() -> Self {
177 Self {
178 times: Vec::new(),
179 values: Vec::new(),
180 easings: Vec::new(),
181 }
182 }
183
184 pub fn add(mut self, time: f64, value: AnimationTarget, easing: Option<Easing>) -> Self {
186 self.times.push(time.clamp(0.0, 1.0));
187 self.values.push(value);
188 self.easings.push(easing.unwrap_or(Easing::Linear));
189 self
190 }
191
192 pub fn interpolate_at(&self, progress: f64) -> Option<AnimationTarget> {
194 if self.values.is_empty() {
195 return None;
196 }
197
198 if self.values.len() == 1 {
199 return Some(self.values[0].clone());
200 }
201
202 let progress = progress.clamp(0.0, 1.0);
203
204 for i in 0..self.times.len() - 1 {
206 if progress >= self.times[i] && progress <= self.times[i + 1] {
207 let segment_progress =
208 (progress - self.times[i]) / (self.times[i + 1] - self.times[i]);
209 let eased_progress = self.easings[i].evaluate(segment_progress);
210
211 return Some(self.interpolate_targets(
212 &self.values[i],
213 &self.values[i + 1],
214 eased_progress,
215 ));
216 }
217 }
218
219 Some(self.values.last().unwrap().clone())
221 }
222
223 fn interpolate_targets(
224 &self,
225 from: &AnimationTarget,
226 to: &AnimationTarget,
227 progress: f64,
228 ) -> AnimationTarget {
229 let mut result = HashMap::new();
230
231 let mut properties: std::collections::HashSet<_> = from.keys().collect();
233 properties.extend(to.keys());
234
235 for property in properties {
236 let from_value = from
237 .get(property)
238 .cloned()
239 .unwrap_or(AnimationValue::Number(0.0));
240 let to_value = to
241 .get(property)
242 .cloned()
243 .unwrap_or(AnimationValue::Number(0.0));
244
245 result.insert(
246 property.clone(),
247 from_value.interpolate(&to_value, progress),
248 );
249 }
250
251 result
252 }
253}
254
255#[macro_export]
257macro_rules! animate {
258 ($($key:expr => $value:expr),* $(,)?) => {
259 {
260 let mut target = std::collections::HashMap::new();
261 $(
262 target.insert($key.to_string(), $value.into());
263 )*
264 target
265 }
266 };
267}
268
269pub mod presets;
272
273impl Default for AnimationBuilder {
274 fn default() -> Self {
275 Self::new()
276 }
277}
278
279impl Default for Variants {
280 fn default() -> Self {
281 Self::new()
282 }
283}
284
285impl Default for Keyframes {
286 fn default() -> Self {
287 Self::new()
288 }
289}
290
291#[cfg(test)]
292mod tests {
293 use super::*;
294 use crate::AnimationPresets;
295
296 #[test]
297 fn test_animation_builder() {
298 let animation = AnimationBuilder::new()
299 .initial(animate!("opacity" => AnimationValue::Number(0.0)))
300 .animate(animate!("opacity" => AnimationValue::Number(1.0)))
301 .transition(Transition {
302 duration: Some(1.0),
303 ..Default::default()
304 })
305 .build();
306
307 assert!(animation.initial.contains_key("opacity"));
308 assert!(animation.animate.contains_key("opacity"));
309 assert_eq!(animation.transition.duration, Some(1.0));
310 }
311
312 #[test]
313 fn test_variants() {
314 let variants = Variants::new()
315 .add_variant("hidden", animate!("opacity" => AnimationValue::Number(0.0)))
316 .add_variant(
317 "visible",
318 animate!("opacity" => AnimationValue::Number(1.0)),
319 )
320 .initial("hidden")
321 .animate("visible");
322
323 assert!(variants.get_variant("hidden").is_some());
324 assert!(variants.get_variant("visible").is_some());
325 assert_eq!(variants.initial, Some("hidden".to_string()));
326 assert_eq!(variants.animate, Some("visible".to_string()));
327 }
328
329 #[test]
330 fn test_keyframes() {
331 let keyframes = Keyframes::new()
332 .add(0.0, animate!("scale" => AnimationValue::Number(1.0)), None)
333 .add(0.5, animate!("scale" => AnimationValue::Number(1.2)), None)
334 .add(1.0, animate!("scale" => AnimationValue::Number(1.0)), None);
335
336 let mid_value = keyframes.interpolate_at(0.25).unwrap();
337 assert!(mid_value.contains_key("scale"));
338
339 if let AnimationValue::Number(scale) = &mid_value["scale"] {
341 assert!(*scale > 1.0 && *scale < 1.2);
342 }
343 }
344
345 #[test]
346 fn test_presets() {
347 let fade_in = AnimationPresets::fade_in();
348 assert!(fade_in.initial.contains_key("opacity"));
349 assert!(fade_in.animate.contains_key("opacity"));
350
351 let slide_up = AnimationPresets::slide_up(50.0);
352 assert!(slide_up.initial.contains_key("y"));
353
354 let bounce = AnimationPresets::bounce();
355 assert_eq!(bounce.times.len(), 3);
356 assert_eq!(bounce.values.len(), 3);
357 }
358
359 #[test]
360 fn test_animate_macro() {
361 let target: HashMap<String, AnimationValue> = animate!(
362 "opacity" => AnimationValue::Number(0.5),
363 "x" => AnimationValue::Pixels(100.0)
364 );
365
366 assert_eq!(target.len(), 2);
367 assert!(target.contains_key("opacity"));
368 assert!(target.contains_key("x"));
369 }
370}