use gpui::{Pixels, px};
use super::Interpolate;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ScrollLink {
start: f32,
end: f32,
suppressed: bool,
}
impl ScrollLink {
pub fn new(start: Pixels, end: Pixels) -> Self {
Self {
start: f32::from(start),
end: f32::from(end),
suppressed: false,
}
}
pub fn over(distance: Pixels) -> Self {
Self::new(px(0.0), distance)
}
pub fn decorative(mut self, reduce_motion: bool) -> Self {
self.suppressed = reduce_motion;
self
}
pub fn progress(self, offset: Pixels) -> f32 {
if self.suppressed {
return 0.0;
}
let offset = f32::from(offset);
let span = self.end - self.start;
if span <= 0.0 {
return if offset >= self.start { 1.0 } else { 0.0 };
}
((offset - self.start) / span).clamp(0.0, 1.0)
}
pub fn sample<T: Interpolate>(self, offset: Pixels, from: T, to: T) -> T {
from.lerp(to, self.progress(offset))
}
}
#[cfg(test)]
mod tests {
use super::*;
use gpui::px;
fn link() -> ScrollLink {
ScrollLink::new(px(20.0), px(120.0))
}
#[test]
fn progress_is_nothing_before_the_range_and_all_of_it_after() {
assert_eq!(link().progress(px(0.0)), 0.0);
assert_eq!(link().progress(px(20.0)), 0.0);
assert_eq!(link().progress(px(120.0)), 1.0);
assert_eq!(link().progress(px(4_000.0)), 1.0);
}
#[test]
fn progress_only_ever_grows_within_the_range() {
let mut previous = 0.0;
for step in 0..=200 {
let progress = link().progress(px(step as f32));
assert!(progress >= previous, "progress went backwards at {step}");
previous = progress;
}
assert_eq!(previous, 1.0);
}
#[test]
fn a_range_with_no_length_is_a_threshold() {
let threshold = ScrollLink::new(px(50.0), px(50.0));
assert_eq!(threshold.progress(px(49.9)), 0.0);
assert_eq!(threshold.progress(px(50.0)), 1.0);
}
#[test]
fn a_sampled_value_travels_across_the_range() {
assert_eq!(ScrollLink::over(px(100.0)).sample(px(50.0), 0.0, 10.0), 5.0);
assert_eq!(
ScrollLink::over(px(100.0)).sample(px(400.0), px(80.0), px(40.0)),
px(40.0)
);
}
#[test]
fn a_decorative_effect_rests_under_reduced_motion() {
let parallax = link().decorative(true);
assert_eq!(parallax.progress(px(80.0)), 0.0);
assert_eq!(parallax.progress(px(400.0)), 0.0);
assert!(link().decorative(false).progress(px(80.0)) > 0.0);
}
}