#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GradientDirection {
Horizontal,
Vertical,
Diagonal,
}
#[derive(Debug, Clone)]
pub struct GradientTextConfig {
pub colors: Vec<[u8; 3]>, pub animation_speed: f64,
pub direction: GradientDirection,
pub yoyo: bool,
}
impl Default for GradientTextConfig {
fn default() -> Self {
Self {
colors: vec![[82, 39, 255], [255, 159, 252], [177, 158, 239]],
animation_speed: 8.0,
direction: GradientDirection::Horizontal,
yoyo: true,
}
}
}
#[derive(Debug)]
pub struct GradientTextState {
elapsed: f64,
}
impl GradientTextState {
pub fn new() -> Self {
Self { elapsed: 0.0 }
}
pub fn update(&mut self, delta_time: f64, config: &GradientTextConfig) -> f32 {
self.elapsed += delta_time;
let animation_duration = config.animation_speed;
if config.yoyo {
let full_cycle = animation_duration * 2.0;
let cycle_time = self.elapsed % full_cycle;
if cycle_time < animation_duration {
((cycle_time / animation_duration) * 100.0) as f32
} else {
let reverse_time = cycle_time - animation_duration;
(100.0 - (reverse_time / animation_duration) * 100.0) as f32
}
} else {
((self.elapsed / animation_duration) * 100.0) as f32
}
}
pub fn background_position(progress: f32, direction: GradientDirection) -> (f32, f32) {
let p = progress / 100.0;
match direction {
GradientDirection::Horizontal => (p, 0.5),
GradientDirection::Vertical => (0.5, p),
GradientDirection::Diagonal => (p, 0.5),
}
}
pub fn reset(&mut self) {
self.elapsed = 0.0;
}
}
impl Default for GradientTextState {
fn default() -> Self {
Self::new()
}
}
pub fn seamless_gradient_colors(colors: &[[u8; 3]]) -> Vec<[u8; 3]> {
if colors.is_empty() {
return vec![];
}
let mut result = colors.to_vec();
result.push(colors[0]);
result
}
pub fn gradient_angle(direction: GradientDirection) -> f32 {
match direction {
GradientDirection::Horizontal => 90.0, GradientDirection::Vertical => 180.0, GradientDirection::Diagonal => 135.0, }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_progress_yoyo() {
let config = GradientTextConfig {
animation_speed: 1.0,
yoyo: true,
..Default::default()
};
let mut state = GradientTextState::new();
let p0 = state.update(0.0, &config);
assert_eq!(p0, 0.0);
state.update(0.5, &config);
let p1 = state.update(0.0, &config);
assert!((p1 - 50.0).abs() < 1.0);
state.update(0.5, &config);
let p2 = state.update(0.0, &config);
assert!((p2 - 100.0).abs() < 1.0);
state.update(0.5, &config);
let p3 = state.update(0.0, &config);
assert!((p3 - 50.0).abs() < 1.0);
state.update(0.5, &config);
let p4 = state.update(0.0, &config);
assert!((p4 - 0.0).abs() < 1.0);
}
#[test]
fn test_progress_continuous() {
let config = GradientTextConfig {
animation_speed: 1.0,
yoyo: false,
..Default::default()
};
let mut state = GradientTextState::new();
state.update(0.5, &config);
let p1 = state.update(0.0, &config);
assert!((p1 - 50.0).abs() < 1.0);
state.update(0.5, &config);
let p2 = state.update(0.0, &config);
assert!((p2 - 100.0).abs() < 1.0);
state.update(0.5, &config);
let p3 = state.update(0.0, &config);
assert!((p3 - 150.0).abs() < 1.0);
}
#[test]
fn test_background_position_horizontal() {
let (x, y) = GradientTextState::background_position(0.0, GradientDirection::Horizontal);
assert_eq!((x, y), (0.0, 0.5));
let (x, y) = GradientTextState::background_position(50.0, GradientDirection::Horizontal);
assert!((x - 0.5).abs() < 0.01);
assert_eq!(y, 0.5);
let (x, y) = GradientTextState::background_position(100.0, GradientDirection::Horizontal);
assert_eq!((x, y), (1.0, 0.5));
}
#[test]
fn test_background_position_vertical() {
let (x, y) = GradientTextState::background_position(0.0, GradientDirection::Vertical);
assert_eq!((x, y), (0.5, 0.0));
let (x, y) = GradientTextState::background_position(100.0, GradientDirection::Vertical);
assert_eq!((x, y), (0.5, 1.0));
}
#[test]
fn test_seamless_gradient() {
let colors = vec![[255, 0, 0], [0, 255, 0], [0, 0, 255]];
let seamless = seamless_gradient_colors(&colors);
assert_eq!(seamless.len(), 4);
assert_eq!(seamless[0], [255, 0, 0]);
assert_eq!(seamless[3], [255, 0, 0]); }
#[test]
fn test_gradient_angle() {
assert_eq!(gradient_angle(GradientDirection::Horizontal), 90.0);
assert_eq!(gradient_angle(GradientDirection::Vertical), 180.0);
assert_eq!(gradient_angle(GradientDirection::Diagonal), 135.0);
}
#[test]
fn test_reset() {
let config = GradientTextConfig::default();
let mut state = GradientTextState::new();
state.update(1.0, &config);
assert!(state.elapsed > 0.0);
state.reset();
assert_eq!(state.elapsed, 0.0);
}
}