1use serde::{Deserialize, Serialize};
7use std::fmt;
8use std::collections::HashMap;
9
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub struct CustomKeyframe {
13 pub name: String,
15 pub steps: Vec<(f32, KeyframeStep)>,
17 pub duration: u32,
19 pub timing_function: TimingFunction,
21 pub delay: u32,
23 pub iteration_count: AnimationIteration,
25 pub direction: AnimationDirection,
27 pub fill_mode: AnimationFillMode,
29 pub play_state: AnimationPlayState,
31}
32
33#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
35pub struct KeyframeStep {
36 pub properties: HashMap<String, String>,
38 pub transform: Option<TransformStep>,
40 pub opacity: Option<f32>,
42 pub color: Option<String>,
44}
45
46#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
48pub struct TransformStep {
49 pub translate: Option<(f32, f32, f32)>,
51 pub scale: Option<(f32, f32, f32)>,
53 pub rotate: Option<(f32, f32, f32)>,
55 pub skew: Option<(f32, f32)>,
57}
58
59#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
61pub enum TimingFunction {
62 Linear,
64 Ease,
66 EaseIn,
68 EaseOut,
70 EaseInOut,
72 CubicBezier(f32, f32, f32, f32),
74 Steps(u32),
76 StepsWithDirection(u32, StepDirection),
78}
79
80#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
82pub enum StepDirection {
83 Start,
85 End,
87}
88
89#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
91pub enum AnimationIteration {
92 Infinite,
94 Count(f32),
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
100pub enum AnimationDirection {
101 Normal,
103 Reverse,
105 Alternate,
107 AlternateReverse,
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
113pub enum AnimationFillMode {
114 None,
116 Forwards,
118 Backwards,
120 Both,
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
126pub enum AnimationPlayState {
127 Running,
129 Paused,
131}
132
133#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
135pub struct AnimationComposition {
136 pub name: String,
138 pub animations: Vec<ComposedAnimation>,
140 pub timing: CompositionTiming,
142}
143
144#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
146pub struct ComposedAnimation {
147 pub animation: AnimationReference,
149 pub start_offset: f32,
151 pub end_offset: f32,
153 pub properties: Option<AnimationProperties>,
155}
156
157#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
159pub enum AnimationReference {
160 BuiltIn(String),
162 Custom(CustomKeyframe),
164}
165
166#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
168pub struct AnimationProperties {
169 pub duration: Option<u32>,
171 pub timing_function: Option<TimingFunction>,
173 pub delay: Option<u32>,
175 pub iteration_count: Option<AnimationIteration>,
177 pub direction: Option<AnimationDirection>,
179}
180
181#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
183pub struct CompositionTiming {
184 pub duration: u32,
186 pub timing_function: TimingFunction,
188 pub delay: u32,
190}
191
192impl CustomKeyframe {
193 pub fn new(name: String) -> Self {
195 Self {
196 name,
197 steps: Vec::new(),
198 duration: 1000,
199 timing_function: TimingFunction::Ease,
200 delay: 0,
201 iteration_count: AnimationIteration::Count(1.0),
202 direction: AnimationDirection::Normal,
203 fill_mode: AnimationFillMode::Both,
204 play_state: AnimationPlayState::Running,
205 }
206 }
207
208 pub fn add_step(&mut self, offset: f32, step: KeyframeStep) {
210 self.steps.push((offset, step));
211 self.steps.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
213 }
214
215 pub fn to_css_keyframes(&self) -> String {
217 let mut css = format!("@keyframes {} {{\n", self.name);
218
219 for (offset, step) in &self.steps {
220 let percentage = (offset * 100.0) as u32;
221 css.push_str(&format!(" {}% {{\n", percentage));
222
223 for (property, value) in &step.properties {
225 css.push_str(&format!(" {}: {};\n", property, value));
226 }
227
228 if let Some(transform) = &step.transform {
230 css.push_str(&format!(" transform: {};\n", transform.to_css_value()));
231 }
232
233 if let Some(opacity) = step.opacity {
235 css.push_str(&format!(" opacity: {};\n", opacity));
236 }
237
238 if let Some(color) = &step.color {
240 css.push_str(&format!(" color: {};\n", color));
241 }
242
243 css.push_str(" }\n");
244 }
245
246 css.push_str("}\n");
247 css
248 }
249
250 pub fn to_class_name(&self) -> String {
252 format!("animate-{}", self.name)
253 }
254
255 pub fn to_css_animation(&self) -> String {
257 format!(
258 "{} {}ms {} {}ms {} {} {} {}",
259 self.name,
260 self.duration,
261 self.timing_function.to_css_value(),
262 self.delay,
263 self.iteration_count.to_css_value(),
264 self.direction.to_css_value(),
265 self.fill_mode.to_css_value(),
266 self.play_state.to_css_value()
267 )
268 }
269}
270
271impl KeyframeStep {
272 pub fn new() -> Self {
274 Self {
275 properties: HashMap::new(),
276 transform: None,
277 opacity: None,
278 color: None,
279 }
280 }
281
282 pub fn add_property(&mut self, property: String, value: String) {
284 self.properties.insert(property, value);
285 }
286
287 pub fn set_transform(&mut self, transform: TransformStep) {
289 self.transform = Some(transform);
290 }
291
292 pub fn set_opacity(&mut self, opacity: f32) {
294 self.opacity = Some(opacity);
295 }
296
297 pub fn set_color(&mut self, color: String) {
299 self.color = Some(color);
300 }
301}
302
303impl TransformStep {
304 pub fn new() -> Self {
306 Self {
307 translate: None,
308 scale: None,
309 rotate: None,
310 skew: None,
311 }
312 }
313
314 pub fn set_translate(&mut self, x: f32, y: f32, z: f32) {
316 self.translate = Some((x, y, z));
317 }
318
319 pub fn set_scale(&mut self, x: f32, y: f32, z: f32) {
321 self.scale = Some((x, y, z));
322 }
323
324 pub fn set_rotate(&mut self, x: f32, y: f32, z: f32) {
326 self.rotate = Some((x, y, z));
327 }
328
329 pub fn set_skew(&mut self, x: f32, y: f32) {
331 self.skew = Some((x, y));
332 }
333
334 pub fn to_css_value(&self) -> String {
336 let mut transforms = Vec::new();
337
338 if let Some((x, y, z)) = self.translate {
339 if z == 0.0 {
340 transforms.push(format!("translate({}px, {}px)", x, y));
341 } else {
342 transforms.push(format!("translate3d({}px, {}px, {}px)", x, y, z));
343 }
344 }
345
346 if let Some((x, y, z)) = self.scale {
347 if z == 1.0 {
348 transforms.push(format!("scale({}, {})", x, y));
349 } else {
350 transforms.push(format!("scale3d({}, {}, {})", x, y, z));
351 }
352 }
353
354 if let Some((x, y, z)) = self.rotate {
355 if x == 0.0 && y == 0.0 {
356 transforms.push(format!("rotate({}deg)", z));
357 } else {
358 transforms.push(format!("rotate3d({}, {}, {}, {}deg)", x, y, z, z));
359 }
360 }
361
362 if let Some((x, y)) = self.skew {
363 transforms.push(format!("skew({}deg, {}deg)", x, y));
364 }
365
366 transforms.join(" ")
367 }
368}
369
370impl TimingFunction {
371 pub fn to_css_value(&self) -> String {
373 match self {
374 TimingFunction::Linear => "linear".to_string(),
375 TimingFunction::Ease => "ease".to_string(),
376 TimingFunction::EaseIn => "ease-in".to_string(),
377 TimingFunction::EaseOut => "ease-out".to_string(),
378 TimingFunction::EaseInOut => "ease-in-out".to_string(),
379 TimingFunction::CubicBezier(x1, y1, x2, y2) => {
380 format!("cubic-bezier({}, {}, {}, {})", x1, y1, x2, y2)
381 }
382 TimingFunction::Steps(count) => format!("steps({})", count),
383 TimingFunction::StepsWithDirection(count, direction) => {
384 let dir = match direction {
385 StepDirection::Start => "start",
386 StepDirection::End => "end",
387 };
388 format!("steps({}, {})", count, dir)
389 }
390 }
391 }
392}
393
394impl AnimationIteration {
395 pub fn to_css_value(&self) -> String {
397 match self {
398 AnimationIteration::Infinite => "infinite".to_string(),
399 AnimationIteration::Count(count) => count.to_string(),
400 }
401 }
402}
403
404impl AnimationDirection {
405 pub fn to_css_value(&self) -> String {
407 match self {
408 AnimationDirection::Normal => "normal".to_string(),
409 AnimationDirection::Reverse => "reverse".to_string(),
410 AnimationDirection::Alternate => "alternate".to_string(),
411 AnimationDirection::AlternateReverse => "alternate-reverse".to_string(),
412 }
413 }
414}
415
416impl AnimationFillMode {
417 pub fn to_css_value(&self) -> String {
419 match self {
420 AnimationFillMode::None => "none".to_string(),
421 AnimationFillMode::Forwards => "forwards".to_string(),
422 AnimationFillMode::Backwards => "backwards".to_string(),
423 AnimationFillMode::Both => "both".to_string(),
424 }
425 }
426}
427
428impl AnimationPlayState {
429 pub fn to_css_value(&self) -> String {
431 match self {
432 AnimationPlayState::Running => "running".to_string(),
433 AnimationPlayState::Paused => "paused".to_string(),
434 }
435 }
436}
437
438impl AnimationComposition {
439 pub fn new(name: String) -> Self {
441 Self {
442 name,
443 animations: Vec::new(),
444 timing: CompositionTiming {
445 duration: 1000,
446 timing_function: TimingFunction::Ease,
447 delay: 0,
448 },
449 }
450 }
451
452 pub fn add_animation(&mut self, animation: ComposedAnimation) {
454 self.animations.push(animation);
455 }
456
457 pub fn to_css(&self) -> String {
459 let mut css = String::new();
460
461 for composed_anim in &self.animations {
463 if let AnimationReference::Custom(keyframe) = &composed_anim.animation {
464 css.push_str(&keyframe.to_css_keyframes());
465 }
466 }
467
468 css.push_str(&format!(".{} {{\n", self.name));
470 css.push_str(&format!(" animation: {} {}ms {} {}ms;\n",
471 self.name,
472 self.timing.duration,
473 self.timing.timing_function.to_css_value(),
474 self.timing.delay
475 ));
476 css.push_str("}\n");
477
478 css
479 }
480
481 pub fn to_class_name(&self) -> String {
483 format!("animate-{}", self.name)
484 }
485}
486
487impl fmt::Display for CustomKeyframe {
488 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
489 write!(f, "{}", self.to_css_animation())
490 }
491}
492
493impl fmt::Display for AnimationComposition {
494 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
495 write!(f, "{}", self.to_css())
496 }
497}
498
499#[cfg(test)]
500mod tests {
501 use super::*;
502
503 #[test]
504 fn test_custom_keyframe_creation() {
505 let mut keyframe = CustomKeyframe::new("fadeIn".to_string());
506 keyframe.duration = 500;
507 keyframe.timing_function = TimingFunction::EaseOut;
508
509 assert_eq!(keyframe.name, "fadeIn");
510 assert_eq!(keyframe.duration, 500);
511 assert_eq!(keyframe.timing_function, TimingFunction::EaseOut);
512 }
513
514 #[test]
515 fn test_keyframe_step_creation() {
516 let mut step = KeyframeStep::new();
517 step.set_opacity(0.0);
518 step.add_property("transform".to_string(), "scale(0.8)".to_string());
519
520 assert_eq!(step.opacity, Some(0.0));
521 assert_eq!(step.properties.get("transform"), Some(&"scale(0.8)".to_string()));
522 }
523
524 #[test]
525 fn test_transform_step_creation() {
526 let mut transform = TransformStep::new();
527 transform.set_translate(10.0, 20.0, 0.0);
528 transform.set_scale(1.2, 1.2, 1.0);
529 transform.set_rotate(0.0, 0.0, 45.0);
530
531 assert_eq!(transform.translate, Some((10.0, 20.0, 0.0)));
532 assert_eq!(transform.scale, Some((1.2, 1.2, 1.0)));
533 assert_eq!(transform.rotate, Some((0.0, 0.0, 45.0)));
534 }
535
536 #[test]
537 fn test_transform_css_generation() {
538 let mut transform = TransformStep::new();
539 transform.set_translate(10.0, 20.0, 0.0);
540 transform.set_scale(1.2, 1.2, 1.0);
541 transform.set_rotate(0.0, 0.0, 45.0);
542
543 let css = transform.to_css_value();
544 assert!(css.contains("translate(10px, 20px)"));
545 assert!(css.contains("scale(1.2, 1.2)"));
546 assert!(css.contains("rotate(45deg)"));
547 }
548
549 #[test]
550 fn test_timing_function_css_values() {
551 assert_eq!(TimingFunction::Linear.to_css_value(), "linear");
552 assert_eq!(TimingFunction::Ease.to_css_value(), "ease");
553 assert_eq!(TimingFunction::EaseIn.to_css_value(), "ease-in");
554 assert_eq!(TimingFunction::EaseOut.to_css_value(), "ease-out");
555 assert_eq!(TimingFunction::EaseInOut.to_css_value(), "ease-in-out");
556
557 let cubic_bezier = TimingFunction::CubicBezier(0.25, 0.1, 0.25, 1.0);
558 assert_eq!(cubic_bezier.to_css_value(), "cubic-bezier(0.25, 0.1, 0.25, 1)");
559
560 let steps = TimingFunction::Steps(5);
561 assert_eq!(steps.to_css_value(), "steps(5)");
562
563 let steps_with_dir = TimingFunction::StepsWithDirection(5, StepDirection::Start);
564 assert_eq!(steps_with_dir.to_css_value(), "steps(5, start)");
565 }
566
567 #[test]
568 fn test_animation_iteration_css_values() {
569 assert_eq!(AnimationIteration::Infinite.to_css_value(), "infinite");
570 assert_eq!(AnimationIteration::Count(3.0).to_css_value(), "3");
571 assert_eq!(AnimationIteration::Count(0.5).to_css_value(), "0.5");
572 }
573
574 #[test]
575 fn test_animation_direction_css_values() {
576 assert_eq!(AnimationDirection::Normal.to_css_value(), "normal");
577 assert_eq!(AnimationDirection::Reverse.to_css_value(), "reverse");
578 assert_eq!(AnimationDirection::Alternate.to_css_value(), "alternate");
579 assert_eq!(AnimationDirection::AlternateReverse.to_css_value(), "alternate-reverse");
580 }
581
582 #[test]
583 fn test_animation_fill_mode_css_values() {
584 assert_eq!(AnimationFillMode::None.to_css_value(), "none");
585 assert_eq!(AnimationFillMode::Forwards.to_css_value(), "forwards");
586 assert_eq!(AnimationFillMode::Backwards.to_css_value(), "backwards");
587 assert_eq!(AnimationFillMode::Both.to_css_value(), "both");
588 }
589
590 #[test]
591 fn test_animation_play_state_css_values() {
592 assert_eq!(AnimationPlayState::Running.to_css_value(), "running");
593 assert_eq!(AnimationPlayState::Paused.to_css_value(), "paused");
594 }
595
596 #[test]
597 fn test_custom_keyframe_css_generation() {
598 let mut keyframe = CustomKeyframe::new("fadeIn".to_string());
599 keyframe.duration = 500;
600 keyframe.timing_function = TimingFunction::EaseOut;
601
602 let mut step0 = KeyframeStep::new();
603 step0.set_opacity(0.0);
604 keyframe.add_step(0.0, step0);
605
606 let mut step1 = KeyframeStep::new();
607 step1.set_opacity(1.0);
608 keyframe.add_step(1.0, step1);
609
610 let css = keyframe.to_css_keyframes();
611 assert!(css.contains("@keyframes fadeIn"));
612 assert!(css.contains("0%"));
613 assert!(css.contains("100%"));
614 assert!(css.contains("opacity: 0"));
615 assert!(css.contains("opacity: 1"));
616
617 let animation_css = keyframe.to_css_animation();
618 assert!(animation_css.contains("fadeIn"));
619 assert!(animation_css.contains("500ms"));
620 assert!(animation_css.contains("ease-out"));
621 }
622
623 #[test]
624 fn test_animation_composition_creation() {
625 let mut composition = AnimationComposition::new("complexAnimation".to_string());
626 composition.timing.duration = 2000;
627 composition.timing.timing_function = TimingFunction::EaseInOut;
628
629 assert_eq!(composition.name, "complexAnimation");
630 assert_eq!(composition.timing.duration, 2000);
631 assert_eq!(composition.timing.timing_function, TimingFunction::EaseInOut);
632 }
633
634 #[test]
635 fn test_animation_composition_css_generation() {
636 let mut composition = AnimationComposition::new("complexAnimation".to_string());
637
638 let mut keyframe = CustomKeyframe::new("fadeIn".to_string());
639 let mut step = KeyframeStep::new();
640 step.set_opacity(1.0);
641 keyframe.add_step(1.0, step);
642
643 let composed_anim = ComposedAnimation {
644 animation: AnimationReference::Custom(keyframe),
645 start_offset: 0.0,
646 end_offset: 1.0,
647 properties: None,
648 };
649
650 composition.add_animation(composed_anim);
651
652 let css = composition.to_css();
653 assert!(css.contains("@keyframes fadeIn"));
654 assert!(css.contains(".complexAnimation"));
655 }
656
657 #[test]
658 fn test_custom_keyframe_class_name() {
659 let keyframe = CustomKeyframe::new("fadeIn".to_string());
660 assert_eq!(keyframe.to_class_name(), "animate-fadeIn");
661 }
662
663 #[test]
664 fn test_animation_composition_class_name() {
665 let composition = AnimationComposition::new("complexAnimation".to_string());
666 assert_eq!(composition.to_class_name(), "animate-complexAnimation");
667 }
668
669 #[test]
670 fn test_custom_keyframe_display() {
671 let keyframe = CustomKeyframe::new("fadeIn".to_string());
672 let display = format!("{}", keyframe);
673 assert!(display.contains("fadeIn"));
674 }
675
676 #[test]
677 fn test_animation_composition_display() {
678 let composition = AnimationComposition::new("complexAnimation".to_string());
679 let display = format!("{}", composition);
680 assert!(display.contains("complexAnimation"));
681 }
682
683 #[test]
684 fn test_custom_keyframe_serialization() {
685 let keyframe = CustomKeyframe::new("fadeIn".to_string());
686 let serialized = serde_json::to_string(&keyframe).unwrap();
687 let deserialized: CustomKeyframe = serde_json::from_str(&serialized).unwrap();
688 assert_eq!(keyframe, deserialized);
689 }
690
691 #[test]
692 fn test_animation_composition_serialization() {
693 let composition = AnimationComposition::new("complexAnimation".to_string());
694 let serialized = serde_json::to_string(&composition).unwrap();
695 let deserialized: AnimationComposition = serde_json::from_str(&serialized).unwrap();
696 assert_eq!(composition, deserialized);
697 }
698}