oxihuman_export/
css_animation_export.rs1#![allow(dead_code)]
4
5pub struct CssKeyframe {
9 pub percent: f32,
10 pub property: String,
11 pub value: String,
12}
13
14pub struct CssAnimation {
16 pub name: String,
17 pub keyframes: Vec<CssKeyframe>,
18 pub duration_ms: u32,
19 pub iteration_count: String,
20 pub timing_function: String,
21}
22
23pub fn new_css_animation(name: &str, duration_ms: u32) -> CssAnimation {
25 CssAnimation {
26 name: name.to_string(),
27 keyframes: Vec::new(),
28 duration_ms,
29 iteration_count: "1".to_string(),
30 timing_function: "ease".to_string(),
31 }
32}
33
34pub fn add_css_keyframe(anim: &mut CssAnimation, percent: f32, property: &str, value: &str) {
36 anim.keyframes.push(CssKeyframe {
37 percent: percent.clamp(0.0, 100.0),
38 property: property.to_string(),
39 value: value.to_string(),
40 });
41}
42
43pub fn css_keyframe_count(anim: &CssAnimation) -> usize {
45 anim.keyframes.len()
46}
47
48pub fn render_css_keyframes(anim: &CssAnimation) -> String {
50 let mut s = format!("@keyframes {} {{\n", anim.name);
51 for kf in &anim.keyframes {
52 s.push_str(&format!(
53 " {}% {{ {}: {}; }}\n",
54 kf.percent, kf.property, kf.value
55 ));
56 }
57 s.push('}');
58 s
59}
60
61pub fn render_css_animation_rule(selector: &str, anim: &CssAnimation) -> String {
63 format!(
64 "{} {{ animation: {} {}ms {} {}; }}",
65 selector, anim.name, anim.duration_ms, anim.timing_function, anim.iteration_count
66 )
67}
68
69pub fn validate_css_animation(anim: &CssAnimation) -> bool {
71 !anim.name.is_empty() && anim.duration_ms > 0
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn new_animation_empty() {
80 let a = new_css_animation("fade", 500);
81 assert_eq!(css_keyframe_count(&a), 0 );
82 }
83
84 #[test]
85 fn add_keyframe_increments() {
86 let mut a = new_css_animation("slide", 300);
87 add_css_keyframe(&mut a, 0.0, "transform", "translateX(0)");
88 assert_eq!(css_keyframe_count(&a), 1 );
89 }
90
91 #[test]
92 fn keyframe_percent_clamped() {
93 let mut a = new_css_animation("x", 100);
94 add_css_keyframe(&mut a, 150.0, "opacity", "1");
95 assert!(a.keyframes[0].percent <= 100.0 );
96 }
97
98 #[test]
99 fn render_keyframes_contains_name() {
100 let a = new_css_animation("pulse", 1000);
101 let css = render_css_keyframes(&a);
102 assert!(css.contains("pulse") );
103 }
104
105 #[test]
106 fn render_keyframes_contains_property() {
107 let mut a = new_css_animation("anim", 1000);
108 add_css_keyframe(&mut a, 50.0, "opacity", "0.5");
109 let css = render_css_keyframes(&a);
110 assert!(css.contains("opacity") );
111 }
112
113 #[test]
114 fn validate_valid_animation() {
115 let a = new_css_animation("valid", 200);
116 assert!(validate_css_animation(&a) );
117 }
118
119 #[test]
120 fn validate_zero_duration_fails() {
121 let a = new_css_animation("zero", 0);
122 assert!(!validate_css_animation(&a) );
123 }
124
125 #[test]
126 fn render_rule_contains_selector() {
127 let a = new_css_animation("bounce", 400);
128 let rule = render_css_animation_rule(".box", &a);
129 assert!(rule.contains(".box") );
130 }
131
132 #[test]
133 fn multiple_keyframes() {
134 let mut a = new_css_animation("m", 1000);
135 add_css_keyframe(&mut a, 0.0, "opacity", "0");
136 add_css_keyframe(&mut a, 50.0, "opacity", "0.5");
137 add_css_keyframe(&mut a, 100.0, "opacity", "1");
138 assert_eq!(css_keyframe_count(&a), 3 );
139 }
140}