Skip to main content

oxihuman_export/
css_animation_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! CSS keyframe animation export stub.
6
7/// A single CSS keyframe stop.
8pub struct CssKeyframe {
9    pub percent: f32,
10    pub property: String,
11    pub value: String,
12}
13
14/// A CSS @keyframes animation.
15pub 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
23/// Create a new CSS animation.
24pub 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
34/// Add a keyframe stop.
35pub 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
43/// Keyframe count.
44pub fn css_keyframe_count(anim: &CssAnimation) -> usize {
45    anim.keyframes.len()
46}
47
48/// Render to a CSS @keyframes string.
49pub 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
61/// Render animation selector rule.
62pub 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
69/// Validate animation (name non-empty, duration > 0).
70pub 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 /* empty */);
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 /* one keyframe */);
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 /* clamped */);
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") /* name in output */);
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") /* property */);
111    }
112
113    #[test]
114    fn validate_valid_animation() {
115        let a = new_css_animation("valid", 200);
116        assert!(validate_css_animation(&a) /* valid */);
117    }
118
119    #[test]
120    fn validate_zero_duration_fails() {
121        let a = new_css_animation("zero", 0);
122        assert!(!validate_css_animation(&a) /* invalid */);
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") /* selector */);
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 /* three keyframes */);
139    }
140}