use crate::{
Bounds, Corners, DevicePixels, Edges, Percentage, Pixels, Point, Radians, Rems, Size,
colors::Colors,
};
use palette::rgb::Rgba;
use std::{
fmt::Debug,
ops::{Add, Mul, Sub},
};
pub trait Lerp<Output = Self>
where
Self: Sized,
{
fn lerp(&self, to: &Self, delta: f32) -> Output;
}
impl Lerp<f32> for bool {
fn lerp(&self, to: &Self, delta: f32) -> f32 {
lerp(*self as u8 as f32, *to as u8 as f32, delta)
}
}
macro_rules! float_lerps {
( $( $ty:ty ),+ ) => {
$(
impl Lerp for $ty {
fn lerp(&self, to: &Self, delta: f32) -> Self {
lerp(*self, *to, delta as $ty)
}
}
)+
};
}
float_lerps!(f32, f64);
macro_rules! int_lerps {
( $( $ty:ident as $ty_into:ident ),+ ) => {
$(
impl Lerp for $ty {
fn lerp(&self, to: &Self, delta: f32) -> Self {
lerp(*self as $ty_into, *to as $ty_into, delta as $ty_into) as $ty
}
}
)+
};
}
int_lerps!(
usize as f32,
u8 as f32,
u16 as f32,
u32 as f32,
u64 as f64,
u128 as f64,
isize as f32,
i8 as f32,
i16 as f32,
i32 as f32,
i64 as f64,
i128 as f64
);
macro_rules! struct_lerps {
( $( $ty:ident $( < $gen:ident > )? { $( $n:ident ),+ } ),+ $(,)? ) => {
$(
impl$(<$gen: Lerp + Clone + Debug + Default + PartialEq>)? Lerp for $ty$(<$gen>)? {
fn lerp(&self, to: &Self, delta: f32) -> Self {
$ty$(::<$gen>)? {
$(
$n: self.$n.lerp(&to.$n, delta)
),+
}
}
}
)+
};
}
struct_lerps!(
Point<T> { x, y },
Size<T> { width, height },
Edges<T> { top, right, bottom, left },
Corners<T> { top_left, top_right, bottom_right, bottom_left },
Bounds<T> { origin, size },
Rgba { color, alpha },
Colors { text, selected_text, background, disabled, selected, border, separator, container }
);
impl Lerp for palette::rgb::Rgb {
fn lerp(&self, to: &Self, delta: f32) -> Self {
Self::new(
self.red.lerp(&to.red, delta),
self.green.lerp(&to.green, delta),
self.blue.lerp(&to.blue, delta),
)
}
}
macro_rules! tuple_struct_lerps {
( $( $ty:ident ( $n:ty ) ),+ ) => {
$(
impl Lerp for $ty {
fn lerp(&self, to: &Self, delta: f32) -> Self {
$ty(self.0.lerp(&to.0, delta))
}
}
)+
};
}
tuple_struct_lerps!(
Radians(f32),
Percentage(f32),
DevicePixels(i32),
Rems(f32),
Pixels(f32)
);
fn lerp<T>(from: T, to: T, alpha: T) -> T
where
T: Copy + Add<Output = T> + Sub<Output = T> + Mul<Output = T>,
{
from + (to - from) * alpha
}
#[cfg(all(test, feature = "test-support"))]
mod tests {
use super::*;
use crate::px;
#[test]
fn test_f32_lerp() {
let start = 0.0_f32;
let end = 100.0_f32;
assert_eq!(start.lerp(&end, 0.0), 0.0);
assert_eq!(start.lerp(&end, 0.5), 50.0);
assert_eq!(start.lerp(&end, 1.0), 100.0);
assert_eq!(start.lerp(&end, 0.25), 25.0);
}
#[test]
fn test_f64_lerp() {
let start = 0.0_f64;
let end = 100.0_f64;
assert_eq!(start.lerp(&end, 0.0), 0.0);
assert_eq!(start.lerp(&end, 0.5), 50.0);
assert_eq!(start.lerp(&end, 1.0), 100.0);
}
#[test]
fn test_f32_lerp_negative_values() {
let start = -50.0_f32;
let end = 50.0_f32;
assert_eq!(start.lerp(&end, 0.0), -50.0);
assert_eq!(start.lerp(&end, 0.5), 0.0);
assert_eq!(start.lerp(&end, 1.0), 50.0);
}
#[test]
fn test_integer_lerp() {
assert_eq!(0_i32.lerp(&100_i32, 0.0), 0);
assert_eq!(0_i32.lerp(&100_i32, 0.5), 50);
assert_eq!(0_i32.lerp(&100_i32, 1.0), 100);
assert_eq!(0_u8.lerp(&100_u8, 0.5), 50);
assert_eq!(0_u16.lerp(&1000_u16, 0.5), 500);
assert_eq!(0_u32.lerp(&10000_u32, 0.5), 5000);
assert_eq!(0_u64.lerp(&100000_u64, 0.5), 50000);
assert_eq!((-50000_i64).lerp(&50000_i64, 0.5), 0);
assert_eq!(0_usize.lerp(&100_usize, 0.5), 50);
assert_eq!((-100_isize).lerp(&100_isize, 0.5), 0);
}
#[test]
fn test_point_lerp() {
let start: Point<f32> = Point { x: 0.0, y: 0.0 };
let end: Point<f32> = Point { x: 100.0, y: 200.0 };
let mid = start.lerp(&end, 0.5);
assert_eq!(mid.x, 50.0);
assert_eq!(mid.y, 100.0);
let at_start = start.lerp(&end, 0.0);
assert_eq!(at_start.x, 0.0);
assert_eq!(at_start.y, 0.0);
let at_end = start.lerp(&end, 1.0);
assert_eq!(at_end.x, 100.0);
assert_eq!(at_end.y, 200.0);
}
#[test]
fn test_size_lerp() {
let start: Size<f32> = Size {
width: 10.0,
height: 20.0,
};
let end: Size<f32> = Size {
width: 110.0,
height: 220.0,
};
let mid = start.lerp(&end, 0.5);
assert_eq!(mid.width, 60.0);
assert_eq!(mid.height, 120.0);
}
#[test]
fn test_edges_lerp() {
let start: Edges<f32> = Edges {
top: 0.0,
right: 0.0,
bottom: 0.0,
left: 0.0,
};
let end: Edges<f32> = Edges {
top: 10.0,
right: 20.0,
bottom: 30.0,
left: 40.0,
};
let mid = start.lerp(&end, 0.5);
assert_eq!(mid.top, 5.0);
assert_eq!(mid.right, 10.0);
assert_eq!(mid.bottom, 15.0);
assert_eq!(mid.left, 20.0);
}
#[test]
fn test_corners_lerp() {
let start: Corners<f32> = Corners {
top_left: 0.0,
top_right: 0.0,
bottom_right: 0.0,
bottom_left: 0.0,
};
let end: Corners<f32> = Corners {
top_left: 4.0,
top_right: 8.0,
bottom_right: 12.0,
bottom_left: 16.0,
};
let mid = start.lerp(&end, 0.5);
assert_eq!(mid.top_left, 2.0);
assert_eq!(mid.top_right, 4.0);
assert_eq!(mid.bottom_right, 6.0);
assert_eq!(mid.bottom_left, 8.0);
}
#[test]
fn test_bounds_lerp() {
let start: Bounds<f32> = Bounds {
origin: Point { x: 0.0, y: 0.0 },
size: Size {
width: 100.0,
height: 100.0,
},
};
let end: Bounds<f32> = Bounds {
origin: Point { x: 50.0, y: 50.0 },
size: Size {
width: 200.0,
height: 200.0,
},
};
let mid = start.lerp(&end, 0.5);
assert_eq!(mid.origin.x, 25.0);
assert_eq!(mid.origin.y, 25.0);
assert_eq!(mid.size.width, 150.0);
assert_eq!(mid.size.height, 150.0);
}
#[test]
fn test_rgba_lerp() {
let start = Rgba::new(0., 0., 0., 1.);
let end = Rgba::new(1., 1., 1., 1.);
let mid = start.lerp(&end, 0.5);
assert_eq!(mid.red, 0.5);
assert_eq!(mid.green, 0.5);
assert_eq!(mid.blue, 0.5);
assert_eq!(mid.alpha, 1.0);
}
#[test]
fn test_rgba_lerp_with_alpha() {
let start = Rgba::new(1., 0., 0., 0.);
let end = Rgba::new(0., 0., 1., 1.);
let mid = start.lerp(&end, 0.5);
assert_eq!(mid.red, 0.5);
assert_eq!(mid.green, 0.0);
assert_eq!(mid.blue, 0.5);
assert_eq!(mid.alpha, 0.5);
}
#[test]
fn test_pixels_lerp() {
let start = px(0.0);
let end = px(100.0);
let mid = start.lerp(&end, 0.5);
assert_eq!(mid, px(50.0));
let at_start = start.lerp(&end, 0.0);
assert_eq!(at_start, px(0.0));
let at_end = start.lerp(&end, 1.0);
assert_eq!(at_end, px(100.0));
}
#[test]
fn test_rems_lerp() {
let start = Rems(0.0);
let end = Rems(2.0);
let mid = start.lerp(&end, 0.5);
assert_eq!(mid.0, 1.0);
}
#[test]
fn test_device_pixels_lerp() {
let start = DevicePixels(0);
let end = DevicePixels(100);
let mid = start.lerp(&end, 0.5);
assert_eq!(mid.0, 50);
}
#[test]
fn test_percentage_lerp() {
let start = Percentage(0.0);
let end = Percentage(100.0);
let mid = start.lerp(&end, 0.5);
assert_eq!(mid.0, 50.0);
}
#[test]
fn test_radians_lerp() {
let start = Radians(0.0);
let end = Radians(std::f32::consts::PI);
let mid = start.lerp(&end, 0.5);
assert!((mid.0 - std::f32::consts::FRAC_PI_2).abs() < 0.0001);
}
}