1use std::{
2 cell::Cell,
3 rc::Rc,
4 time::{Duration, Instant},
5};
6
7use crate::{
8 AnyElement, App, Element, ElementId, GlobalElementId, InspectorElementId, IntoElement, Window,
9};
10
11pub use easing::*;
12use smallvec::SmallVec;
13
14#[derive(Clone)]
16pub struct AnimationHandle {
17 cancelled: Rc<Cell<bool>>,
18}
19
20impl AnimationHandle {
21 fn new() -> Self {
22 Self {
23 cancelled: Rc::new(Cell::new(false)),
24 }
25 }
26
27 pub fn cancel(&self) {
29 self.cancelled.set(true);
30 }
31
32 pub fn is_cancelled(&self) -> bool {
34 self.cancelled.get()
35 }
36}
37
38#[derive(Clone)]
40pub struct Animation {
41 pub duration: Duration,
43 pub oneshot: bool,
45 pub easing: Rc<dyn Fn(f32) -> f32>,
48}
49
50impl Animation {
51 pub fn new(duration: Duration) -> Self {
54 Self {
55 duration,
56 oneshot: true,
57 easing: Rc::new(linear),
58 }
59 }
60
61 pub fn repeat(mut self) -> Self {
63 self.oneshot = false;
64 self
65 }
66
67 pub fn with_easing(mut self, easing: impl Fn(f32) -> f32 + 'static) -> Self {
71 self.easing = Rc::new(easing);
72 self
73 }
74}
75
76pub trait AnimationExt {
78 fn with_animation(
80 self,
81 id: impl Into<ElementId>,
82 animation: Animation,
83 animator: impl Fn(Self, f32) -> Self + 'static,
84 ) -> AnimationElement<Self>
85 where
86 Self: Sized,
87 {
88 AnimationElement {
89 id: id.into(),
90 element: Some(self),
91 animator: Box::new(move |this, _, value| animator(this, value)),
92 animations: smallvec::smallvec![animation],
93 cancel_handle: None,
94 }
95 }
96
97 fn with_animations(
99 self,
100 id: impl Into<ElementId>,
101 animations: Vec<Animation>,
102 animator: impl Fn(Self, usize, f32) -> Self + 'static,
103 ) -> AnimationElement<Self>
104 where
105 Self: Sized,
106 {
107 AnimationElement {
108 id: id.into(),
109 element: Some(self),
110 animator: Box::new(animator),
111 animations: animations.into(),
112 cancel_handle: None,
113 }
114 }
115
116 fn with_cancellable_animation(
119 self,
120 id: impl Into<ElementId>,
121 animation: Animation,
122 animator: impl Fn(Self, f32) -> Self + 'static,
123 ) -> (AnimationElement<Self>, AnimationHandle)
124 where
125 Self: Sized,
126 {
127 let handle = AnimationHandle::new();
128 let element = AnimationElement {
129 id: id.into(),
130 element: Some(self),
131 animator: Box::new(move |this, _, value| animator(this, value)),
132 animations: smallvec::smallvec![animation],
133 cancel_handle: Some(handle.cancelled.clone()),
134 };
135 (element, handle)
136 }
137}
138
139impl<E: IntoElement + 'static> AnimationExt for E {}
140
141pub struct AnimationElement<E> {
143 id: ElementId,
144 element: Option<E>,
145 animations: SmallVec<[Animation; 1]>,
146 animator: Box<dyn Fn(E, usize, f32) -> E + 'static>,
147 cancel_handle: Option<Rc<Cell<bool>>>,
148}
149
150impl<E> AnimationElement<E> {
151 pub fn map_element(mut self, f: impl FnOnce(E) -> E) -> AnimationElement<E> {
154 self.element = self.element.map(f);
155 self
156 }
157}
158
159impl<E: IntoElement + 'static> IntoElement for AnimationElement<E> {
160 type Element = AnimationElement<E>;
161
162 fn into_element(self) -> Self::Element {
163 self
164 }
165}
166
167struct AnimationState {
168 start: Instant,
169 animation_ix: usize,
170}
171
172impl<E: IntoElement + 'static> Element for AnimationElement<E> {
173 type RequestLayoutState = AnyElement;
174 type PrepaintState = ();
175
176 fn id(&self) -> Option<ElementId> {
177 Some(self.id.clone())
178 }
179
180 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
181 None
182 }
183
184 fn request_layout(
185 &mut self,
186 global_id: Option<&GlobalElementId>,
187 _inspector_id: Option<&InspectorElementId>,
188 window: &mut Window,
189 cx: &mut App,
190 ) -> (crate::LayoutId, Self::RequestLayoutState) {
191 window.with_element_state(global_id.unwrap(), |state, window| {
192 let mut state = state.unwrap_or_else(|| AnimationState {
193 start: Instant::now(),
194 animation_ix: 0,
195 });
196
197 let cancelled = self.cancel_handle.as_ref().map_or(false, |h| h.get());
198
199 let animation_ix = state.animation_ix;
200
201 let (delta, done) = if cancelled {
202 (1.0_f32, true)
203 } else {
204 let mut delta = state.start.elapsed().as_secs_f32()
205 / self.animations[animation_ix].duration.as_secs_f32();
206
207 let mut done = false;
208 if delta > 1.0 {
209 if self.animations[animation_ix].oneshot {
210 if animation_ix >= self.animations.len() - 1 {
211 done = true;
212 } else {
213 state.start = Instant::now();
214 state.animation_ix += 1;
215 }
216 delta = 1.0;
217 } else {
218 delta %= 1.0;
219 }
220 }
221 let delta = (self.animations[animation_ix].easing)(delta);
222 (delta, done)
223 };
224
225 debug_assert!(
226 (0.0..=1.0).contains(&delta),
227 "delta should always be between 0 and 1"
228 );
229
230 let element = self.element.take().expect("should only be called once");
231 let mut element = (self.animator)(element, animation_ix, delta).into_any_element();
232
233 if !done {
234 window.request_animation_frame();
235 }
236
237 ((element.request_layout(window, cx), element), state)
238 })
239 }
240
241 fn prepaint(
242 &mut self,
243 _id: Option<&GlobalElementId>,
244 _inspector_id: Option<&InspectorElementId>,
245 _bounds: crate::Bounds<crate::Pixels>,
246 element: &mut Self::RequestLayoutState,
247 window: &mut Window,
248 cx: &mut App,
249 ) -> Self::PrepaintState {
250 element.prepaint(window, cx);
251 }
252
253 fn paint(
254 &mut self,
255 _id: Option<&GlobalElementId>,
256 _inspector_id: Option<&InspectorElementId>,
257 _bounds: crate::Bounds<crate::Pixels>,
258 element: &mut Self::RequestLayoutState,
259 _: &mut Self::PrepaintState,
260 window: &mut Window,
261 cx: &mut App,
262 ) {
263 element.paint(window, cx);
264 }
265}
266
267mod easing {
268 use std::f32::consts::PI;
269
270 pub fn linear(delta: f32) -> f32 {
272 delta
273 }
274
275 pub fn quadratic(delta: f32) -> f32 {
277 delta * delta
278 }
279
280 pub fn ease_in_out(delta: f32) -> f32 {
282 if delta < 0.5 {
283 2.0 * delta * delta
284 } else {
285 let x = -2.0 * delta + 2.0;
286 1.0 - x * x / 2.0
287 }
288 }
289
290 pub fn ease_out_quint() -> impl Fn(f32) -> f32 {
292 move |delta| 1.0 - (1.0 - delta).powi(5)
293 }
294
295 pub fn bounce(easing: impl Fn(f32) -> f32) -> impl Fn(f32) -> f32 {
297 move |delta| {
298 if delta < 0.5 {
299 easing(delta * 2.0)
300 } else {
301 easing((1.0 - delta) * 2.0)
302 }
303 }
304 }
305
306 pub fn pulsating_between(min: f32, max: f32) -> impl Fn(f32) -> f32 {
308 let range = max - min;
309
310 move |delta| {
311 let t = (delta * 2.0 * PI).sin();
313 let breath = (t * t * t + t) / 2.0;
314
315 let normalized_alpha = (breath + 1.0) / 2.0;
317
318 min + (normalized_alpha * range)
319 }
320 }
321}