1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::fmt;
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 Default for KeyframeStep {
272 fn default() -> Self {
273 Self::new()
274 }
275}
276
277impl KeyframeStep {
278 pub fn new() -> Self {
280 Self {
281 properties: HashMap::new(),
282 transform: None,
283 opacity: None,
284 color: None,
285 }
286 }
287
288 pub fn add_property(&mut self, property: String, value: String) {
290 self.properties.insert(property, value);
291 }
292
293 pub fn set_transform(&mut self, transform: TransformStep) {
295 self.transform = Some(transform);
296 }
297
298 pub fn set_opacity(&mut self, opacity: f32) {
300 self.opacity = Some(opacity);
301 }
302
303 pub fn set_color(&mut self, color: String) {
305 self.color = Some(color);
306 }
307}
308
309impl Default for TransformStep {
310 fn default() -> Self {
311 Self::new()
312 }
313}
314
315impl TransformStep {
316 pub fn new() -> Self {
318 Self {
319 translate: None,
320 scale: None,
321 rotate: None,
322 skew: None,
323 }
324 }
325
326 pub fn set_translate(&mut self, x: f32, y: f32, z: f32) {
328 self.translate = Some((x, y, z));
329 }
330
331 pub fn set_scale(&mut self, x: f32, y: f32, z: f32) {
333 self.scale = Some((x, y, z));
334 }
335
336 pub fn set_rotate(&mut self, x: f32, y: f32, z: f32) {
338 self.rotate = Some((x, y, z));
339 }
340
341 pub fn set_skew(&mut self, x: f32, y: f32) {
343 self.skew = Some((x, y));
344 }
345
346 pub fn to_css_value(&self) -> String {
348 let mut transforms = Vec::new();
349
350 if let Some((x, y, z)) = self.translate {
351 if z == 0.0 {
352 transforms.push(format!("translate({}px, {}px)", x, y));
353 } else {
354 transforms.push(format!("translate3d({}px, {}px, {}px)", x, y, z));
355 }
356 }
357
358 if let Some((x, y, z)) = self.scale {
359 if z == 1.0 {
360 transforms.push(format!("scale({}, {})", x, y));
361 } else {
362 transforms.push(format!("scale3d({}, {}, {})", x, y, z));
363 }
364 }
365
366 if let Some((x, y, z)) = self.rotate {
367 if x == 0.0 && y == 0.0 {
368 transforms.push(format!("rotate({}deg)", z));
369 } else {
370 transforms.push(format!("rotate3d({}, {}, {}, {}deg)", x, y, z, z));
371 }
372 }
373
374 if let Some((x, y)) = self.skew {
375 transforms.push(format!("skew({}deg, {}deg)", x, y));
376 }
377
378 transforms.join(" ")
379 }
380}
381
382impl TimingFunction {
383 pub fn to_css_value(&self) -> String {
385 match self {
386 TimingFunction::Linear => "linear".to_string(),
387 TimingFunction::Ease => "ease".to_string(),
388 TimingFunction::EaseIn => "ease-in".to_string(),
389 TimingFunction::EaseOut => "ease-out".to_string(),
390 TimingFunction::EaseInOut => "ease-in-out".to_string(),
391 TimingFunction::CubicBezier(x1, y1, x2, y2) => {
392 format!("cubic-bezier({}, {}, {}, {})", x1, y1, x2, y2)
393 }
394 TimingFunction::Steps(count) => format!("steps({})", count),
395 TimingFunction::StepsWithDirection(count, direction) => {
396 let dir = match direction {
397 StepDirection::Start => "start",
398 StepDirection::End => "end",
399 };
400 format!("steps({}, {})", count, dir)
401 }
402 }
403 }
404}
405
406impl AnimationIteration {
407 pub fn to_css_value(&self) -> String {
409 match self {
410 AnimationIteration::Infinite => "infinite".to_string(),
411 AnimationIteration::Count(count) => count.to_string(),
412 }
413 }
414}
415
416impl AnimationDirection {
417 pub fn to_css_value(&self) -> String {
419 match self {
420 AnimationDirection::Normal => "normal".to_string(),
421 AnimationDirection::Reverse => "reverse".to_string(),
422 AnimationDirection::Alternate => "alternate".to_string(),
423 AnimationDirection::AlternateReverse => "alternate-reverse".to_string(),
424 }
425 }
426}
427
428impl AnimationFillMode {
429 pub fn to_css_value(&self) -> String {
431 match self {
432 AnimationFillMode::None => "none".to_string(),
433 AnimationFillMode::Forwards => "forwards".to_string(),
434 AnimationFillMode::Backwards => "backwards".to_string(),
435 AnimationFillMode::Both => "both".to_string(),
436 }
437 }
438}
439
440impl AnimationPlayState {
441 pub fn to_css_value(&self) -> String {
443 match self {
444 AnimationPlayState::Running => "running".to_string(),
445 AnimationPlayState::Paused => "paused".to_string(),
446 }
447 }
448}
449
450impl AnimationComposition {
451 pub fn new(name: String) -> Self {
453 Self {
454 name,
455 animations: Vec::new(),
456 timing: CompositionTiming {
457 duration: 1000,
458 timing_function: TimingFunction::Ease,
459 delay: 0,
460 },
461 }
462 }
463
464 pub fn add_animation(&mut self, animation: ComposedAnimation) {
466 self.animations.push(animation);
467 }
468
469 pub fn to_css(&self) -> String {
471 let mut css = String::new();
472
473 for composed_anim in &self.animations {
475 if let AnimationReference::Custom(keyframe) = &composed_anim.animation {
476 css.push_str(&keyframe.to_css_keyframes());
477 }
478 }
479
480 css.push_str(&format!(".{} {{\n", self.name));
482 css.push_str(&format!(
483 " animation: {} {}ms {} {}ms;\n",
484 self.name,
485 self.timing.duration,
486 self.timing.timing_function.to_css_value(),
487 self.timing.delay
488 ));
489 css.push_str("}\n");
490
491 css
492 }
493
494 pub fn to_class_name(&self) -> String {
496 format!("animate-{}", self.name)
497 }
498}
499
500impl fmt::Display for CustomKeyframe {
501 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
502 write!(f, "{}", self.to_css_animation())
503 }
504}
505
506impl fmt::Display for AnimationComposition {
507 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
508 write!(f, "{}", self.to_css())
509 }
510}
511
512#[cfg(test)]
513mod tests {
514 use super::*;
515
516 #[test]
517 fn test_custom_keyframe_creation() {
518 let mut keyframe = CustomKeyframe::new("fadeIn".to_string());
519 keyframe.duration = 500;
520 keyframe.timing_function = TimingFunction::EaseOut;
521
522 assert_eq!(keyframe.name, "fadeIn");
523 assert_eq!(keyframe.duration, 500);
524 assert_eq!(keyframe.timing_function, TimingFunction::EaseOut);
525 }
526
527 #[test]
528 fn test_keyframe_step_creation() {
529 let mut step = KeyframeStep::new();
530 step.set_opacity(0.0);
531 step.add_property("transform".to_string(), "scale(0.8)".to_string());
532
533 assert_eq!(step.opacity, Some(0.0));
534 assert_eq!(
535 step.properties.get("transform"),
536 Some(&"scale(0.8)".to_string())
537 );
538 }
539
540 #[test]
541 fn test_transform_step_creation() {
542 let mut transform = TransformStep::new();
543 transform.set_translate(10.0, 20.0, 0.0);
544 transform.set_scale(1.2, 1.2, 1.0);
545 transform.set_rotate(0.0, 0.0, 45.0);
546
547 assert_eq!(transform.translate, Some((10.0, 20.0, 0.0)));
548 assert_eq!(transform.scale, Some((1.2, 1.2, 1.0)));
549 assert_eq!(transform.rotate, Some((0.0, 0.0, 45.0)));
550 }
551
552 #[test]
553 fn test_transform_css_generation() {
554 let mut transform = TransformStep::new();
555 transform.set_translate(10.0, 20.0, 0.0);
556 transform.set_scale(1.2, 1.2, 1.0);
557 transform.set_rotate(0.0, 0.0, 45.0);
558
559 let css = transform.to_css_value();
560 assert!(css.contains("translate(10px, 20px)"));
561 assert!(css.contains("scale(1.2, 1.2)"));
562 assert!(css.contains("rotate(45deg)"));
563 }
564
565 #[test]
566 fn test_timing_function_css_values() {
567 assert_eq!(TimingFunction::Linear.to_css_value(), "linear");
568 assert_eq!(TimingFunction::Ease.to_css_value(), "ease");
569 assert_eq!(TimingFunction::EaseIn.to_css_value(), "ease-in");
570 assert_eq!(TimingFunction::EaseOut.to_css_value(), "ease-out");
571 assert_eq!(TimingFunction::EaseInOut.to_css_value(), "ease-in-out");
572
573 let cubic_bezier = TimingFunction::CubicBezier(0.25, 0.1, 0.25, 1.0);
574 assert_eq!(
575 cubic_bezier.to_css_value(),
576 "cubic-bezier(0.25, 0.1, 0.25, 1)"
577 );
578
579 let steps = TimingFunction::Steps(5);
580 assert_eq!(steps.to_css_value(), "steps(5)");
581
582 let steps_with_dir = TimingFunction::StepsWithDirection(5, StepDirection::Start);
583 assert_eq!(steps_with_dir.to_css_value(), "steps(5, start)");
584 }
585
586 #[test]
587 fn test_animation_iteration_css_values() {
588 assert_eq!(AnimationIteration::Infinite.to_css_value(), "infinite");
589 assert_eq!(AnimationIteration::Count(3.0).to_css_value(), "3");
590 assert_eq!(AnimationIteration::Count(0.5).to_css_value(), "0.5");
591 }
592
593 #[test]
594 fn test_animation_direction_css_values() {
595 assert_eq!(AnimationDirection::Normal.to_css_value(), "normal");
596 assert_eq!(AnimationDirection::Reverse.to_css_value(), "reverse");
597 assert_eq!(AnimationDirection::Alternate.to_css_value(), "alternate");
598 assert_eq!(
599 AnimationDirection::AlternateReverse.to_css_value(),
600 "alternate-reverse"
601 );
602 }
603
604 #[test]
605 fn test_animation_fill_mode_css_values() {
606 assert_eq!(AnimationFillMode::None.to_css_value(), "none");
607 assert_eq!(AnimationFillMode::Forwards.to_css_value(), "forwards");
608 assert_eq!(AnimationFillMode::Backwards.to_css_value(), "backwards");
609 assert_eq!(AnimationFillMode::Both.to_css_value(), "both");
610 }
611
612 #[test]
613 fn test_animation_play_state_css_values() {
614 assert_eq!(AnimationPlayState::Running.to_css_value(), "running");
615 assert_eq!(AnimationPlayState::Paused.to_css_value(), "paused");
616 }
617
618 #[test]
619 fn test_custom_keyframe_css_generation() {
620 let mut keyframe = CustomKeyframe::new("fadeIn".to_string());
621 keyframe.duration = 500;
622 keyframe.timing_function = TimingFunction::EaseOut;
623
624 let mut step0 = KeyframeStep::new();
625 step0.set_opacity(0.0);
626 keyframe.add_step(0.0, step0);
627
628 let mut step1 = KeyframeStep::new();
629 step1.set_opacity(1.0);
630 keyframe.add_step(1.0, step1);
631
632 let css = keyframe.to_css_keyframes();
633 assert!(css.contains("@keyframes fadeIn"));
634 assert!(css.contains("0%"));
635 assert!(css.contains("100%"));
636 assert!(css.contains("opacity: 0"));
637 assert!(css.contains("opacity: 1"));
638
639 let animation_css = keyframe.to_css_animation();
640 assert!(animation_css.contains("fadeIn"));
641 assert!(animation_css.contains("500ms"));
642 assert!(animation_css.contains("ease-out"));
643 }
644
645 #[test]
646 fn test_animation_composition_creation() {
647 let mut composition = AnimationComposition::new("complexAnimation".to_string());
648 composition.timing.duration = 2000;
649 composition.timing.timing_function = TimingFunction::EaseInOut;
650
651 assert_eq!(composition.name, "complexAnimation");
652 assert_eq!(composition.timing.duration, 2000);
653 assert_eq!(
654 composition.timing.timing_function,
655 TimingFunction::EaseInOut
656 );
657 }
658
659 #[test]
660 fn test_animation_composition_css_generation() {
661 let mut composition = AnimationComposition::new("complexAnimation".to_string());
662
663 let mut keyframe = CustomKeyframe::new("fadeIn".to_string());
664 let mut step = KeyframeStep::new();
665 step.set_opacity(1.0);
666 keyframe.add_step(1.0, step);
667
668 let composed_anim = ComposedAnimation {
669 animation: AnimationReference::Custom(keyframe),
670 start_offset: 0.0,
671 end_offset: 1.0,
672 properties: None,
673 };
674
675 composition.add_animation(composed_anim);
676
677 let css = composition.to_css();
678 assert!(css.contains("@keyframes fadeIn"));
679 assert!(css.contains(".complexAnimation"));
680 }
681
682 #[test]
683 fn test_custom_keyframe_class_name() {
684 let keyframe = CustomKeyframe::new("fadeIn".to_string());
685 assert_eq!(keyframe.to_class_name(), "animate-fadeIn");
686 }
687
688 #[test]
689 fn test_animation_composition_class_name() {
690 let composition = AnimationComposition::new("complexAnimation".to_string());
691 assert_eq!(composition.to_class_name(), "animate-complexAnimation");
692 }
693
694 #[test]
695 fn test_custom_keyframe_display() {
696 let keyframe = CustomKeyframe::new("fadeIn".to_string());
697 let display = format!("{}", keyframe);
698 assert!(display.contains("fadeIn"));
699 }
700
701 #[test]
702 fn test_animation_composition_display() {
703 let composition = AnimationComposition::new("complexAnimation".to_string());
704 let display = format!("{}", composition);
705 assert!(display.contains("complexAnimation"));
706 }
707
708 #[test]
709 fn test_custom_keyframe_serialization() {
710 let keyframe = CustomKeyframe::new("fadeIn".to_string());
711 let serialized = serde_json::to_string(&keyframe).unwrap();
712 let deserialized: CustomKeyframe = serde_json::from_str(&serialized).unwrap();
713 assert_eq!(keyframe, deserialized);
714 }
715
716 #[test]
717 fn test_animation_composition_serialization() {
718 let composition = AnimationComposition::new("complexAnimation".to_string());
719 let serialized = serde_json::to_string(&composition).unwrap();
720 let deserialized: AnimationComposition = serde_json::from_str(&serialized).unwrap();
721 assert_eq!(composition, deserialized);
722 }
723}