#[derive(Debug, Clone)]
pub struct ScrollVelocityConfig {
pub base_velocity: f32,
pub damping: f32,
pub stiffness: f32,
pub num_copies: usize,
pub velocity_input_range: [f32; 2],
pub velocity_output_range: [f32; 2],
}
impl Default for ScrollVelocityConfig {
fn default() -> Self {
Self {
base_velocity: 100.0,
damping: 50.0,
stiffness: 400.0,
num_copies: 6,
velocity_input_range: [0.0, 1000.0],
velocity_output_range: [0.0, 5.0],
}
}
}
pub struct ScrollVelocity {
config: ScrollVelocityConfig,
base_x: f32,
direction_factor: f32,
velocity_spring: SpringState,
}
#[derive(Debug, Clone, Copy)]
struct SpringState {
value: f32,
velocity: f32,
}
impl SpringState {
fn new() -> Self {
Self {
value: 0.0,
velocity: 0.0,
}
}
fn update(&mut self, target: f32, damping: f32, stiffness: f32, dt: f32) {
let force = (target - self.value) * stiffness;
let damping_force = self.velocity * damping;
self.velocity += (force - damping_force) * dt;
self.value += self.velocity * dt;
}
}
impl Default for ScrollVelocity {
fn default() -> Self {
Self::new(ScrollVelocityConfig::default())
}
}
impl ScrollVelocity {
pub fn new(config: ScrollVelocityConfig) -> Self {
Self {
config,
base_x: 0.0,
direction_factor: 1.0,
velocity_spring: SpringState::new(),
}
}
pub fn update(&mut self, scroll_velocity: f32, delta_time: f32) {
self.velocity_spring.update(
scroll_velocity,
self.config.damping,
self.config.stiffness,
delta_time,
);
let velocity_factor = self.map_velocity(self.velocity_spring.value);
if velocity_factor < 0.0 {
self.direction_factor = -1.0;
} else if velocity_factor > 0.0 {
self.direction_factor = 1.0;
}
let base_move = self.direction_factor * self.config.base_velocity * delta_time;
let velocity_boost = self.direction_factor * base_move * velocity_factor;
let total_move = base_move + velocity_boost;
self.base_x += total_move;
}
pub fn x_offset(&self, text_width: f32) -> f32 {
if text_width <= 0.0 {
return 0.0;
}
self.wrap(-text_width, 0.0, self.base_x)
}
fn map_velocity(&self, velocity: f32) -> f32 {
let [in_min, in_max] = self.config.velocity_input_range;
let [out_min, out_max] = self.config.velocity_output_range;
if in_max == in_min {
return out_min;
}
let t = (velocity - in_min) / (in_max - in_min);
out_min + (out_max - out_min) * t
}
fn wrap(&self, min: f32, max: f32, v: f32) -> f32 {
let range = max - min;
if range <= 0.0 {
return min;
}
let offset = v - min;
let wrapped = ((offset % range) + range) % range;
wrapped + min
}
pub fn reset(&mut self) {
self.base_x = 0.0;
self.direction_factor = 1.0;
self.velocity_spring = SpringState::new();
}
pub fn set_config(&mut self, config: ScrollVelocityConfig) {
self.config = config;
}
pub fn config(&self) -> &ScrollVelocityConfig {
&self.config
}
pub fn num_copies(&self) -> usize {
self.config.num_copies
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_initial_state() {
let velocity = ScrollVelocity::default();
assert_eq!(velocity.x_offset(100.0), -100.0);
}
#[test]
fn test_forward_movement() {
let mut velocity = ScrollVelocity::default();
velocity.update(0.0, 0.1);
let offset = velocity.x_offset(100.0);
assert!(offset > -100.0 && offset <= 0.0);
}
#[test]
fn test_direction_reversal() {
let mut velocity = ScrollVelocity::default();
velocity.update(100.0, 0.1);
let dir1 = velocity.direction_factor;
velocity.update(-100.0, 0.5); let dir2 = velocity.direction_factor;
assert_eq!(dir1, 1.0);
assert_eq!(dir2, -1.0);
}
#[test]
fn test_wrapping() {
let mut velocity = ScrollVelocity::default();
for _ in 0..20 {
velocity.update(0.0, 0.1);
}
let offset = velocity.x_offset(100.0);
assert!(offset >= -100.0 && offset <= 0.0);
}
#[test]
fn test_velocity_mapping() {
let velocity = ScrollVelocity::default();
assert_eq!(velocity.map_velocity(0.0), 0.0);
assert_eq!(velocity.map_velocity(1000.0), 5.0);
assert!((velocity.map_velocity(500.0) - 2.5).abs() < 0.001);
}
#[test]
fn test_custom_velocity() {
let config = ScrollVelocityConfig {
base_velocity: 200.0,
..Default::default()
};
let mut velocity = ScrollVelocity::new(config);
velocity.update(0.0, 0.1);
let offset = velocity.base_x;
assert!((offset - 20.0).abs() < 0.1);
}
#[test]
fn test_spring_smoothing() {
let mut velocity = ScrollVelocity::default();
velocity.update(1000.0, 0.016);
let velocity1 = velocity.velocity_spring.value;
assert!(velocity1 < 1000.0);
for _ in 0..10 {
velocity.update(1000.0, 0.016);
}
let velocity2 = velocity.velocity_spring.value;
assert!(velocity2 > velocity1);
}
#[test]
fn test_reset() {
let mut velocity = ScrollVelocity::default();
velocity.update(100.0, 0.5);
velocity.reset();
assert_eq!(velocity.base_x, 0.0);
assert_eq!(velocity.direction_factor, 1.0);
assert_eq!(velocity.velocity_spring.value, 0.0);
}
}