Skip to main content

gpui/
lerp.rs

1//! Lerp trait defines behaviour for interpolating between two values of the same type.
2use crate::{
3    Bounds, Corners, DevicePixels, Edges, Percentage, Pixels, Point, Radians, Rems, Size,
4    colors::Colors,
5};
6use palette::rgb::Rgba;
7use std::{
8    fmt::Debug,
9    ops::{Add, Mul, Sub},
10};
11
12/// A trait for types that can be linearly interpolated.
13pub trait Lerp<Output = Self>
14where
15    Self: Sized,
16{
17    /// Interpolates between `self` and `to` based on `delta`.
18    fn lerp(&self, to: &Self, delta: f32) -> Output;
19}
20
21impl Lerp<f32> for bool {
22    fn lerp(&self, to: &Self, delta: f32) -> f32 {
23        lerp(*self as u8 as f32, *to as u8 as f32, delta)
24    }
25}
26
27macro_rules! float_lerps {
28    ( $( $ty:ty ),+ ) => {
29        $(
30            impl Lerp for $ty {
31                fn lerp(&self, to: &Self, delta: f32) -> Self {
32                    lerp(*self, *to, delta as $ty)
33                }
34            }
35        )+
36    };
37}
38
39float_lerps!(f32, f64);
40
41macro_rules! int_lerps {
42    ( $( $ty:ident as $ty_into:ident ),+ ) => {
43        $(
44            impl Lerp for $ty {
45                fn lerp(&self, to: &Self, delta: f32) -> Self {
46                    lerp(*self as $ty_into, *to as $ty_into, delta as $ty_into) as $ty
47                }
48            }
49        )+
50    };
51}
52
53int_lerps!(
54    usize as f32,
55    u8 as f32,
56    u16 as f32,
57    u32 as f32,
58    u64 as f64,
59    u128 as f64,
60    isize as f32,
61    i8 as f32,
62    i16 as f32,
63    i32 as f32,
64    i64 as f64,
65    i128 as f64
66);
67
68macro_rules! struct_lerps {
69    ( $( $ty:ident $( < $gen:ident > )? { $( $n:ident ),+ } ),+ $(,)? ) => {
70        $(
71            impl$(<$gen: Lerp + Clone + Debug + Default + PartialEq>)? Lerp for $ty$(<$gen>)? {
72                fn lerp(&self, to: &Self, delta: f32) -> Self {
73                    $ty$(::<$gen>)? {
74                        $(
75                            $n: self.$n.lerp(&to.$n, delta)
76                        ),+
77                    }
78                }
79            }
80        )+
81    };
82}
83
84struct_lerps!(
85    Point<T> { x, y },
86    Size<T> { width, height },
87    Edges<T> { top, right, bottom, left },
88    Corners<T> { top_left, top_right, bottom_right, bottom_left },
89    Bounds<T> { origin, size },
90    Rgba { color, alpha },
91    Colors { text, selected_text, background, disabled, selected, border, separator, container }
92);
93
94impl Lerp for palette::rgb::Rgb {
95    fn lerp(&self, to: &Self, delta: f32) -> Self {
96        Self::new(
97            self.red.lerp(&to.red, delta),
98            self.green.lerp(&to.green, delta),
99            self.blue.lerp(&to.blue, delta),
100        )
101    }
102}
103
104macro_rules! tuple_struct_lerps {
105    ( $( $ty:ident ( $n:ty ) ),+ ) => {
106        $(
107            impl Lerp for $ty {
108                fn lerp(&self, to: &Self, delta: f32) -> Self {
109                    $ty(self.0.lerp(&to.0, delta))
110                }
111            }
112        )+
113    };
114}
115
116tuple_struct_lerps!(
117    Radians(f32),
118    Percentage(f32),
119    DevicePixels(i32),
120    Rems(f32),
121    Pixels(f32)
122);
123
124fn lerp<T>(from: T, to: T, alpha: T) -> T
125where
126    T: Copy + Add<Output = T> + Sub<Output = T> + Mul<Output = T>,
127{
128    from + (to - from) * alpha
129}
130
131#[cfg(all(test, feature = "test-support"))]
132mod tests {
133    use super::*;
134    use crate::px;
135
136    #[test]
137    fn test_f32_lerp() {
138        let start = 0.0_f32;
139        let end = 100.0_f32;
140
141        assert_eq!(start.lerp(&end, 0.0), 0.0);
142        assert_eq!(start.lerp(&end, 0.5), 50.0);
143        assert_eq!(start.lerp(&end, 1.0), 100.0);
144        assert_eq!(start.lerp(&end, 0.25), 25.0);
145    }
146
147    #[test]
148    fn test_f64_lerp() {
149        let start = 0.0_f64;
150        let end = 100.0_f64;
151
152        assert_eq!(start.lerp(&end, 0.0), 0.0);
153        assert_eq!(start.lerp(&end, 0.5), 50.0);
154        assert_eq!(start.lerp(&end, 1.0), 100.0);
155    }
156
157    #[test]
158    fn test_f32_lerp_negative_values() {
159        let start = -50.0_f32;
160        let end = 50.0_f32;
161
162        assert_eq!(start.lerp(&end, 0.0), -50.0);
163        assert_eq!(start.lerp(&end, 0.5), 0.0);
164        assert_eq!(start.lerp(&end, 1.0), 50.0);
165    }
166
167    #[test]
168    fn test_integer_lerp() {
169        // i32
170        assert_eq!(0_i32.lerp(&100_i32, 0.0), 0);
171        assert_eq!(0_i32.lerp(&100_i32, 0.5), 50);
172        assert_eq!(0_i32.lerp(&100_i32, 1.0), 100);
173
174        // u8
175        assert_eq!(0_u8.lerp(&100_u8, 0.5), 50);
176
177        // u16
178        assert_eq!(0_u16.lerp(&1000_u16, 0.5), 500);
179
180        // u32
181        assert_eq!(0_u32.lerp(&10000_u32, 0.5), 5000);
182
183        // u64
184        assert_eq!(0_u64.lerp(&100000_u64, 0.5), 50000);
185
186        // i64
187        assert_eq!((-50000_i64).lerp(&50000_i64, 0.5), 0);
188
189        // usize
190        assert_eq!(0_usize.lerp(&100_usize, 0.5), 50);
191
192        // isize
193        assert_eq!((-100_isize).lerp(&100_isize, 0.5), 0);
194    }
195
196    #[test]
197    fn test_point_lerp() {
198        let start: Point<f32> = Point { x: 0.0, y: 0.0 };
199        let end: Point<f32> = Point { x: 100.0, y: 200.0 };
200
201        let mid = start.lerp(&end, 0.5);
202        assert_eq!(mid.x, 50.0);
203        assert_eq!(mid.y, 100.0);
204
205        let at_start = start.lerp(&end, 0.0);
206        assert_eq!(at_start.x, 0.0);
207        assert_eq!(at_start.y, 0.0);
208
209        let at_end = start.lerp(&end, 1.0);
210        assert_eq!(at_end.x, 100.0);
211        assert_eq!(at_end.y, 200.0);
212    }
213
214    #[test]
215    fn test_size_lerp() {
216        let start: Size<f32> = Size {
217            width: 10.0,
218            height: 20.0,
219        };
220        let end: Size<f32> = Size {
221            width: 110.0,
222            height: 220.0,
223        };
224
225        let mid = start.lerp(&end, 0.5);
226        assert_eq!(mid.width, 60.0);
227        assert_eq!(mid.height, 120.0);
228    }
229
230    #[test]
231    fn test_edges_lerp() {
232        let start: Edges<f32> = Edges {
233            top: 0.0,
234            right: 0.0,
235            bottom: 0.0,
236            left: 0.0,
237        };
238        let end: Edges<f32> = Edges {
239            top: 10.0,
240            right: 20.0,
241            bottom: 30.0,
242            left: 40.0,
243        };
244
245        let mid = start.lerp(&end, 0.5);
246        assert_eq!(mid.top, 5.0);
247        assert_eq!(mid.right, 10.0);
248        assert_eq!(mid.bottom, 15.0);
249        assert_eq!(mid.left, 20.0);
250    }
251
252    #[test]
253    fn test_corners_lerp() {
254        let start: Corners<f32> = Corners {
255            top_left: 0.0,
256            top_right: 0.0,
257            bottom_right: 0.0,
258            bottom_left: 0.0,
259        };
260        let end: Corners<f32> = Corners {
261            top_left: 4.0,
262            top_right: 8.0,
263            bottom_right: 12.0,
264            bottom_left: 16.0,
265        };
266
267        let mid = start.lerp(&end, 0.5);
268        assert_eq!(mid.top_left, 2.0);
269        assert_eq!(mid.top_right, 4.0);
270        assert_eq!(mid.bottom_right, 6.0);
271        assert_eq!(mid.bottom_left, 8.0);
272    }
273
274    #[test]
275    fn test_bounds_lerp() {
276        let start: Bounds<f32> = Bounds {
277            origin: Point { x: 0.0, y: 0.0 },
278            size: Size {
279                width: 100.0,
280                height: 100.0,
281            },
282        };
283        let end: Bounds<f32> = Bounds {
284            origin: Point { x: 50.0, y: 50.0 },
285            size: Size {
286                width: 200.0,
287                height: 200.0,
288            },
289        };
290
291        let mid = start.lerp(&end, 0.5);
292        assert_eq!(mid.origin.x, 25.0);
293        assert_eq!(mid.origin.y, 25.0);
294        assert_eq!(mid.size.width, 150.0);
295        assert_eq!(mid.size.height, 150.0);
296    }
297
298    #[test]
299    fn test_rgba_lerp() {
300        let start = Rgba::new(0., 0., 0., 1.);
301        let end = Rgba::new(1., 1., 1., 1.);
302
303        let mid = start.lerp(&end, 0.5);
304        assert_eq!(mid.red, 0.5);
305        assert_eq!(mid.green, 0.5);
306        assert_eq!(mid.blue, 0.5);
307        assert_eq!(mid.alpha, 1.0);
308    }
309
310    #[test]
311    fn test_rgba_lerp_with_alpha() {
312        let start = Rgba::new(1., 0., 0., 0.);
313        let end = Rgba::new(0., 0., 1., 1.);
314
315        let mid = start.lerp(&end, 0.5);
316        assert_eq!(mid.red, 0.5);
317        assert_eq!(mid.green, 0.0);
318        assert_eq!(mid.blue, 0.5);
319        assert_eq!(mid.alpha, 0.5);
320    }
321
322    #[test]
323    fn test_pixels_lerp() {
324        let start = px(0.0);
325        let end = px(100.0);
326
327        let mid = start.lerp(&end, 0.5);
328        assert_eq!(mid, px(50.0));
329
330        let at_start = start.lerp(&end, 0.0);
331        assert_eq!(at_start, px(0.0));
332
333        let at_end = start.lerp(&end, 1.0);
334        assert_eq!(at_end, px(100.0));
335    }
336
337    #[test]
338    fn test_rems_lerp() {
339        let start = Rems(0.0);
340        let end = Rems(2.0);
341
342        let mid = start.lerp(&end, 0.5);
343        assert_eq!(mid.0, 1.0);
344    }
345
346    #[test]
347    fn test_device_pixels_lerp() {
348        let start = DevicePixels(0);
349        let end = DevicePixels(100);
350
351        let mid = start.lerp(&end, 0.5);
352        assert_eq!(mid.0, 50);
353    }
354
355    #[test]
356    fn test_percentage_lerp() {
357        let start = Percentage(0.0);
358        let end = Percentage(100.0);
359
360        let mid = start.lerp(&end, 0.5);
361        assert_eq!(mid.0, 50.0);
362    }
363
364    #[test]
365    fn test_radians_lerp() {
366        let start = Radians(0.0);
367        let end = Radians(std::f32::consts::PI);
368
369        let mid = start.lerp(&end, 0.5);
370        assert!((mid.0 - std::f32::consts::FRAC_PI_2).abs() < 0.0001);
371    }
372}