#[derive(Debug, Clone)]
pub struct ShinyTextConfig {
pub speed: f64,
pub direction_left: bool,
pub yoyo: bool,
pub delay: f64,
pub spread: f32,
}
impl Default for ShinyTextConfig {
fn default() -> Self {
Self {
speed: 2.0,
direction_left: true,
yoyo: false,
delay: 0.0,
spread: 120.0,
}
}
}
#[derive(Debug)]
pub struct ShinyTextState {
elapsed: f64,
direction: f32, }
impl ShinyTextState {
pub fn new(direction_left: bool) -> Self {
Self {
elapsed: 0.0,
direction: if direction_left { 1.0 } else { -1.0 },
}
}
pub fn update(&mut self, delta_time: f64, config: &ShinyTextConfig) -> f32 {
self.elapsed += delta_time;
let animation_duration = config.speed;
let delay_duration = config.delay;
if config.yoyo {
let cycle_duration = animation_duration + delay_duration;
let full_cycle = cycle_duration * 2.0;
let cycle_time = self.elapsed % full_cycle;
if cycle_time < animation_duration {
let p = (cycle_time / animation_duration) * 100.0;
if self.direction > 0.0 {
p as f32
} else {
100.0 - p as f32
}
} else if cycle_time < cycle_duration {
if self.direction > 0.0 {
100.0
} else {
0.0
}
} else if cycle_time < cycle_duration + animation_duration {
let reverse_time = cycle_time - cycle_duration;
let p = 100.0 - (reverse_time / animation_duration) * 100.0;
if self.direction > 0.0 {
p as f32
} else {
100.0 - p as f32
}
} else {
if self.direction > 0.0 {
0.0
} else {
100.0
}
}
} else {
let cycle_duration = animation_duration + delay_duration;
let cycle_time = self.elapsed % cycle_duration;
if cycle_time < animation_duration {
let p = (cycle_time / animation_duration) * 100.0;
if self.direction > 0.0 {
p as f32
} else {
100.0 - p as f32
}
} else {
if self.direction > 0.0 {
100.0
} else {
0.0
}
}
}
}
pub fn background_position(progress: f32) -> f32 {
let pos_percent = 150.0 - progress * 2.0;
(pos_percent + 50.0) / 200.0
}
pub fn reset(&mut self, direction_left: bool) {
self.elapsed = 0.0;
self.direction = if direction_left { 1.0 } else { -1.0 };
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_progress_forward() {
let config = ShinyTextConfig {
speed: 1.0,
delay: 0.0,
yoyo: false,
..Default::default()
};
let mut state = ShinyTextState::new(true);
let p1 = state.update(0.5, &config);
assert!((p1 - 50.0).abs() < 1.0);
let p2 = state.update(0.49, &config);
assert!((p2 - 99.0).abs() < 2.0);
}
#[test]
fn test_background_position() {
let pos0 = ShinyTextState::background_position(0.0);
assert!((pos0 - 1.0).abs() < 0.01);
let pos100 = ShinyTextState::background_position(100.0);
assert!((pos100 - 0.0).abs() < 0.01);
let pos50 = ShinyTextState::background_position(50.0);
assert!((pos50 - 0.5).abs() < 0.01);
}
#[test]
fn test_yoyo_mode() {
let config = ShinyTextConfig {
speed: 1.0,
delay: 0.0,
yoyo: true,
..Default::default()
};
let mut state = ShinyTextState::new(true);
state.update(0.5, &config);
let p_forward = state.update(0.0, &config);
assert!((p_forward - 50.0).abs() < 1.0);
state.update(0.5, &config);
let p_end = state.update(0.0, &config);
assert!((p_end - 100.0).abs() < 1.0);
state.update(0.5, &config);
let p_reverse = state.update(0.0, &config);
assert!((p_reverse - 50.0).abs() < 1.0);
}
#[test]
fn test_direction_right() {
let config = ShinyTextConfig {
speed: 1.0,
delay: 0.0,
yoyo: false,
direction_left: false,
..Default::default()
};
let mut state = ShinyTextState::new(false);
let p0 = state.update(0.0, &config);
assert_eq!(p0, 100.0);
state.update(0.5, &config);
let p1 = state.update(0.0, &config);
assert!((p1 - 50.0).abs() < 1.0);
}
}