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 RequestLayoutState = AnyElement;
89
90 type PrepaintState = ();
91
92 fn request_layout(
93 &mut self,
94 cx: &mut crate::ElementContext,
95 ) -> (crate::LayoutId, Self::RequestLayoutState) {
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.request_layout(cx), element), Some(state))
134 })
135 }
136
137 fn prepaint(
138 &mut self,
139 _bounds: crate::Bounds<crate::Pixels>,
140 element: &mut Self::RequestLayoutState,
141 cx: &mut crate::ElementContext,
142 ) -> Self::PrepaintState {
143 element.prepaint(cx);
144 }
145
146 fn paint(
147 &mut self,
148 _bounds: crate::Bounds<crate::Pixels>,
149 element: &mut Self::RequestLayoutState,
150 _: &mut Self::PrepaintState,
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}