Skip to main content

gpui/
lerp.rs

1//! Interpolation between two values of the same type.
2use crate::{
3    AbsoluteLength, Background, Bounds, Corners, DefiniteLength, DevicePixels, Edges, Fill, Length,
4    Percentage, Pixels, Point, Radians, Relative, Rems, RingColor, Size, colors::Colors,
5};
6use palette::{
7    Hsla, IntoColor, Oklab, Oklaba,
8    rgb::{Rgb, Rgba},
9};
10use std::{
11    fmt::Debug,
12    ops::{Add, Mul, Sub},
13};
14
15/// A type whose values can be interpolated between two endpoints.
16///
17/// `delta` is the fraction of the way from `self` to `to`. `0.0` gives `self`, and `1.0` gives
18/// `to`. For numbers, deltas outside that range extrapolate. [`Animated`](crate::Animated),
19/// [`Transition`](crate::Transition) and style transitions call `lerp` for every frame.
20///
21/// Types that cannot be meaningfully blended, such as a gradient [`Background`](crate::Background)
22/// or a length that mixes pixels with rems, hold `self` until `delta` reaches `1.0`, then switch to
23/// `to`. [`Hsla`](crate::Hsla) values are converted through RGB; [`Interpolate`](crate::Interpolate)
24/// instead takes the shorter path around the hue wheel.
25///
26/// # Examples
27///
28/// ```
29/// use gpui::{Lerp, px};
30///
31/// assert_eq!(0.0_f32.lerp(&10.0, 0.25), 2.5);
32/// assert_eq!(px(0.).lerp(&px(10.), 0.5), px(5.));
33///
34/// // A bool blends to a number.
35/// let halfway: f32 = false.lerp(&true, 0.5);
36/// assert_eq!(halfway, 0.5);
37/// ```
38pub trait Lerp<Output = Self>
39where
40    Self: Sized,
41{
42    /// Returns the value `delta` of the way from `self` to `to`.
43    fn lerp(&self, to: &Self, delta: f32) -> Output;
44}
45
46impl Lerp<f32> for bool {
47    fn lerp(&self, to: &Self, delta: f32) -> f32 {
48        lerp(*self as u8 as f32, *to as u8 as f32, delta)
49    }
50}
51
52macro_rules! float_lerps {
53    ( $( $ty:ty ),+ ) => {
54        $(
55            impl Lerp for $ty {
56                fn lerp(&self, to: &Self, delta: f32) -> Self {
57                    lerp(*self, *to, delta as $ty)
58                }
59            }
60        )+
61    };
62}
63
64float_lerps!(f32, f64);
65
66macro_rules! int_lerps {
67    ( $( $ty:ident as $ty_into:ident ),+ ) => {
68        $(
69            impl Lerp for $ty {
70                fn lerp(&self, to: &Self, delta: f32) -> Self {
71                    lerp(*self as $ty_into, *to as $ty_into, delta as $ty_into) as $ty
72                }
73            }
74        )+
75    };
76}
77
78int_lerps!(
79    usize as f32,
80    u8 as f32,
81    u16 as f32,
82    u32 as f32,
83    u64 as f64,
84    u128 as f64,
85    isize as f32,
86    i8 as f32,
87    i16 as f32,
88    i32 as f32,
89    i64 as f64,
90    i128 as f64
91);
92
93macro_rules! struct_lerps {
94    ( $( $ty:ident $( < $gen:ident > )? { $( $n:ident ),+ } ),+ $(,)? ) => {
95        $(
96            impl$(<$gen: Lerp + Clone + Debug + Default + PartialEq>)? Lerp for $ty$(<$gen>)? {
97                fn lerp(&self, to: &Self, delta: f32) -> Self {
98                    $ty$(::<$gen>)? {
99                        $(
100                            $n: self.$n.lerp(&to.$n, delta)
101                        ),+
102                    }
103                }
104            }
105        )+
106    };
107}
108
109struct_lerps!(
110    Point<T> { x, y },
111    Size<T> { width, height },
112    Edges<T> { top, right, bottom, left },
113    Corners<T> { top_left, top_right, bottom_right, bottom_left },
114    Bounds<T> { origin, size },
115    Rgba { color, alpha },
116    Colors { text, selected_text, background, disabled, selected, border, separator, container }
117);
118
119macro_rules! tuple_struct_lerps {
120    ( $( $ty:ident ( $n:ty ) ),+ ) => {
121        $(
122            impl Lerp for $ty {
123                fn lerp(&self, to: &Self, delta: f32) -> Self {
124                    $ty(self.0.lerp(&to.0, delta))
125                }
126            }
127        )+
128    };
129}
130
131tuple_struct_lerps!(
132    Radians(f32),
133    Percentage(f32),
134    DevicePixels(i32),
135    Relative(f32),
136    Rems(f32),
137    Pixels(f32)
138);
139
140macro_rules! new_constructor_lerps {
141    ( $( $ty:ident $( < $gen:ident > )? ::new ( $( $n:ident ),+ ) ),+ $(,)? ) => {
142        $(
143            impl$(<$gen: Lerp + Clone + Debug + Default + PartialEq>)? Lerp for $ty$(<$gen>)? {
144                fn lerp(&self, to: &Self, delta: f32) -> Self {
145                    $ty$(::<$gen>)?::new(
146                        $(
147                            self.$n.lerp(&to.$n, delta)
148                        ),+
149                    )
150                }
151            }
152        )+
153    };
154}
155
156new_constructor_lerps!(
157    Rgb::new(red, green, blue),
158    Oklab::new(l, a, b),
159    Oklaba::new(l, a, b, alpha)
160);
161
162impl Lerp for Hsla {
163    fn lerp(&self, to: &Self, delta: f32) -> Self {
164        if delta <= 0.0 {
165            return *self;
166        }
167        if delta >= 1.0 {
168            return *to;
169        }
170
171        let from: Rgba = (*self).into_color();
172        let to: Rgba = (*to).into_color();
173        from.lerp(&to, delta).into_color()
174    }
175}
176
177impl Lerp for RingColor {
178    fn lerp(&self, to: &Self, delta: f32) -> Self {
179        match (*self, *to) {
180            (Self::Color(from), Self::Color(to)) => Self::Color(from.lerp(&to, delta)),
181            (_, to) if delta >= 1.0 => to,
182            (from, _) => from,
183        }
184    }
185}
186
187impl Lerp for AbsoluteLength {
188    fn lerp(&self, to: &Self, delta: f32) -> Self {
189        match (*self, *to) {
190            (Self::Pixels(from), Self::Pixels(to)) => Self::Pixels(from.lerp(&to, delta)),
191            (Self::Rems(from), Self::Rems(to)) => Self::Rems(from.lerp(&to, delta)),
192            (from, Self::Pixels(to)) if from.is_zero() => {
193                Self::Pixels(Pixels::default().lerp(&to, delta))
194            }
195            (from, Self::Rems(to)) if from.is_zero() => {
196                Self::Rems(Rems::default().lerp(&to, delta))
197            }
198            (Self::Pixels(from), to) if to.is_zero() => {
199                Self::Pixels(from.lerp(&Pixels::default(), delta))
200            }
201            (Self::Rems(from), to) if to.is_zero() => {
202                Self::Rems(from.lerp(&Rems::default(), delta))
203            }
204            _ if delta >= 1.0 => *to,
205            _ => *self,
206        }
207    }
208}
209
210impl Lerp for DefiniteLength {
211    fn lerp(&self, to: &Self, delta: f32) -> Self {
212        match (*self, *to) {
213            (Self::Absolute(from), Self::Absolute(to)) => Self::Absolute(from.lerp(&to, delta)),
214            (Self::Relative(from), Self::Relative(to)) => Self::Relative(from.lerp(&to, delta)),
215            _ if delta >= 1.0 => *to,
216            _ => *self,
217        }
218    }
219}
220
221impl Lerp for Length {
222    fn lerp(&self, to: &Self, delta: f32) -> Self {
223        match (*self, *to) {
224            (Self::Definite(from), Self::Definite(to)) => Self::Definite(from.lerp(&to, delta)),
225            _ if delta >= 1.0 => *to,
226            _ => *self,
227        }
228    }
229}
230
231impl Lerp for Background {
232    fn lerp(&self, to: &Self, delta: f32) -> Self {
233        if delta <= 0.0 {
234            return *self;
235        }
236        if delta >= 1.0 {
237            return *to;
238        }
239
240        match (self.as_solid(), to.as_solid()) {
241            (Some(from), Some(to_color)) => {
242                Background::from(from.lerp(&to_color, delta)).color_space(to.interpolation_space())
243            }
244            _ => *self,
245        }
246    }
247}
248
249impl Lerp for Fill {
250    fn lerp(&self, to: &Self, delta: f32) -> Self {
251        match (self, to) {
252            (Self::Color(from), Self::Color(to)) => Self::Color(from.lerp(to, delta)),
253        }
254    }
255}
256
257fn lerp<T>(from: T, to: T, alpha: T) -> T
258where
259    T: Copy + Add<Output = T> + Sub<Output = T> + Mul<Output = T>,
260{
261    from + (to - from) * alpha
262}