Files
oak-gpui/crates/gpui/src/lerp.rs
T

384 lines
9.0 KiB
Rust

//! Lerp trait defines behaviour for interpolating between two values of the same type.
use std::{
fmt::Debug,
ops::{Add, Mul, Sub},
};
use crate::{
Bounds, Corners, DevicePixels, Edges, Percentage, Pixels, Point, Radians, Rems, Rgba, Size,
colors::Colors,
};
/// A trait for types that can be linearly interpolated.
pub trait Lerp<Output = Self>
where
Self: Sized,
{
/// Interpolates between `self` and `to` based on `delta`.
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 { r, g, b, a },
Colors { text, selected_text, background, disabled, selected, border, separator, container }
);
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() {
// i32
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);
// u8
assert_eq!(0_u8.lerp(&100_u8, 0.5), 50);
// u16
assert_eq!(0_u16.lerp(&1000_u16, 0.5), 500);
// u32
assert_eq!(0_u32.lerp(&10000_u32, 0.5), 5000);
// u64
assert_eq!(0_u64.lerp(&100000_u64, 0.5), 50000);
// i64
assert_eq!((-50000_i64).lerp(&50000_i64, 0.5), 0);
// usize
assert_eq!(0_usize.lerp(&100_usize, 0.5), 50);
// isize
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 {
r: 0.0,
g: 0.0,
b: 0.0,
a: 1.0,
};
let end = Rgba {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0,
};
let mid = start.lerp(&end, 0.5);
assert_eq!(mid.r, 0.5);
assert_eq!(mid.g, 0.5);
assert_eq!(mid.b, 0.5);
assert_eq!(mid.a, 1.0);
}
#[test]
fn test_rgba_lerp_with_alpha() {
let start = Rgba {
r: 1.0,
g: 0.0,
b: 0.0,
a: 0.0,
};
let end = Rgba {
r: 0.0,
g: 0.0,
b: 1.0,
a: 1.0,
};
let mid = start.lerp(&end, 0.5);
assert_eq!(mid.r, 0.5);
assert_eq!(mid.g, 0.0);
assert_eq!(mid.b, 0.5);
assert_eq!(mid.a, 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);
}
}