animation.rs

  1use std::time::{Duration, Instant};
  2
  3use crate::{AnyElement, Element, ElementId, IntoElement};
  4
  5pub use easing::*;
  6
  7/// An animation that can be applied to an element.
  8pub struct Animation {
  9    /// The amount of time for which this animation should run
 10    pub duration: Duration,
 11    /// Whether to repeat this animation when it finishes
 12    pub oneshot: bool,
 13    /// A function that takes a delta between 0 and 1 and returns a new delta
 14    /// between 0 and 1 based on the given easing function.
 15    pub easing: Box<dyn Fn(f32) -> f32>,
 16}
 17
 18impl Animation {
 19    /// Create a new animation with the given duration.
 20    /// By default the animation will only run once and will use a linear easing function.
 21    pub fn new(duration: Duration) -> Self {
 22        Self {
 23            duration,
 24            oneshot: true,
 25            easing: Box::new(linear),
 26        }
 27    }
 28
 29    /// Set the animation to loop when it finishes.
 30    pub fn repeat(mut self) -> Self {
 31        self.oneshot = false;
 32        self
 33    }
 34
 35    /// Set the easing function to use for this animation.
 36    /// The easing function will take a time delta between 0 and 1 and return a new delta
 37    /// between 0 and 1
 38    pub fn with_easing(mut self, easing: impl Fn(f32) -> f32 + 'static) -> Self {
 39        self.easing = Box::new(easing);
 40        self
 41    }
 42}
 43
 44/// An extension trait for adding the animation wrapper to both Elements and Components
 45pub trait AnimationExt {
 46    /// Render this component or element with an animation
 47    fn with_animation(
 48        self,
 49        id: impl Into<ElementId>,
 50        animation: Animation,
 51        animator: impl Fn(Self, f32) -> Self + 'static,
 52    ) -> AnimationElement<Self>
 53    where
 54        Self: Sized,
 55    {
 56        AnimationElement {
 57            id: id.into(),
 58            element: Some(self),
 59            animator: Box::new(animator),
 60            animation,
 61        }
 62    }
 63}
 64
 65impl<E> AnimationExt for E {}
 66
 67/// A GPUI element that applies an animation to another element
 68pub struct AnimationElement<E> {
 69    id: ElementId,
 70    element: Option<E>,
 71    animation: Animation,
 72    animator: Box<dyn Fn(E, f32) -> E + 'static>,
 73}
 74
 75impl<E: IntoElement + 'static> IntoElement for AnimationElement<E> {
 76    type Element = AnimationElement<E>;
 77
 78    fn into_element(self) -> Self::Element {
 79        self
 80    }
 81}
 82
 83struct AnimationState {
 84    start: Instant,
 85}
 86
 87impl<E: IntoElement + 'static> Element for AnimationElement<E> {
 88    type BeforeLayout = AnyElement;
 89
 90    type AfterLayout = ();
 91
 92    fn before_layout(
 93        &mut self,
 94        cx: &mut crate::ElementContext,
 95    ) -> (crate::LayoutId, Self::BeforeLayout) {
 96        cx.with_element_state(Some(self.id.clone()), |state, cx| {
 97            let state = state.unwrap().unwrap_or_else(|| AnimationState {
 98                start: Instant::now(),
 99            });
100            let mut delta =
101                state.start.elapsed().as_secs_f32() / self.animation.duration.as_secs_f32();
102
103            let mut done = false;
104            if delta > 1.0 {
105                if self.animation.oneshot {
106                    done = true;
107                    delta = 1.0;
108                } else {
109                    delta = delta % 1.0;
110                }
111            }
112            let delta = (self.animation.easing)(delta);
113
114            debug_assert!(
115                delta >= 0.0 && delta <= 1.0,
116                "delta should always be between 0 and 1"
117            );
118
119            let element = self.element.take().expect("should only be called once");
120            let mut element = (self.animator)(element, delta).into_any_element();
121
122            if !done {
123                let parent_id = cx.parent_view_id();
124                cx.on_next_frame(move |cx| {
125                    if let Some(parent_id) = parent_id {
126                        cx.notify(parent_id)
127                    } else {
128                        cx.refresh()
129                    }
130                })
131            }
132
133            ((element.before_layout(cx), element), Some(state))
134        })
135    }
136
137    fn after_layout(
138        &mut self,
139        _bounds: crate::Bounds<crate::Pixels>,
140        element: &mut Self::BeforeLayout,
141        cx: &mut crate::ElementContext,
142    ) -> Self::AfterLayout {
143        element.after_layout(cx);
144    }
145
146    fn paint(
147        &mut self,
148        _bounds: crate::Bounds<crate::Pixels>,
149        element: &mut Self::BeforeLayout,
150        _: &mut Self::AfterLayout,
151        cx: &mut crate::ElementContext,
152    ) {
153        element.paint(cx);
154    }
155}
156
157mod easing {
158    /// The linear easing function, or delta itself
159    pub fn linear(delta: f32) -> f32 {
160        delta
161    }
162
163    /// The quadratic easing function, delta * delta
164    pub fn quadratic(delta: f32) -> f32 {
165        delta * delta
166    }
167
168    /// The quadratic ease-in-out function, which starts and ends slowly but speeds up in the middle
169    pub fn ease_in_out(delta: f32) -> f32 {
170        if delta < 0.5 {
171            2.0 * delta * delta
172        } else {
173            let x = -2.0 * delta + 2.0;
174            1.0 - x * x / 2.0
175        }
176    }
177
178    /// Apply the given easing function, first in the forward direction and then in the reverse direction
179    pub fn bounce(easing: impl Fn(f32) -> f32) -> impl Fn(f32) -> f32 {
180        move |delta| {
181            if delta < 0.5 {
182                easing(delta * 2.0)
183            } else {
184                easing((1.0 - delta) * 2.0)
185            }
186        }
187    }
188}