rgpui_component/
animation.rs1use std::{rc::Rc, time::Duration};
2
3use rgpui::{
4 Animation, AnimationExt, ElementId, IntoElement, Pixels, Point, Styled, point,
5 prelude::FluentBuilder, px,
6};
7use smallvec::SmallVec;
8
9pub fn cubic_bezier(x1: f32, y1: f32, x2: f32, y2: f32) -> impl Fn(f32) -> f32 {
15 move |t: f32| {
16 let one_t = 1.0 - t;
17 let one_t2 = one_t * one_t;
18 let t2 = t * t;
19 let t3 = t2 * t;
20
21 let _x = 3.0 * x1 * one_t2 * t + 3.0 * x2 * one_t * t2 + t3;
23 let y = 3.0 * y1 * one_t2 * t + 3.0 * y2 * one_t * t2 + t3;
24
25 y
26 }
27}
28
29pub fn ease_out_cubic(t: f32) -> f32 {
33 let t = t.clamp(0.0, 1.0);
34 1.0 - (1.0 - t).powi(3)
35}
36
37pub fn ease_in_cubic(t: f32) -> f32 {
39 let t = t.clamp(0.0, 1.0);
40 t * t * t
41}
42
43pub fn ease_in_out_cubic(t: f32) -> f32 {
45 let t = t.clamp(0.0, 1.0);
46 if t < 0.5 {
47 4.0 * t * t * t
48 } else {
49 1.0 - (-2.0 * t + 2.0).powi(3) / 2.0
50 }
51}
52
53pub trait Lerp: Clone {
57 fn lerp(&self, target: &Self, t: f32) -> Self;
58}
59
60impl Lerp for f32 {
61 fn lerp(&self, target: &Self, t: f32) -> Self {
62 self + (target - self) * t
63 }
64}
65
66impl Lerp for Pixels {
67 fn lerp(&self, target: &Self, t: f32) -> Self {
68 let a: f32 = (*self).into();
69 let b: f32 = (*target).into();
70 px(a + (b - a) * t)
71 }
72}
73
74impl Lerp for Point<Pixels> {
75 fn lerp(&self, target: &Self, t: f32) -> Self {
76 point(
77 Lerp::lerp(&self.x, &target.x, t),
78 Lerp::lerp(&self.y, &target.y, t),
79 )
80 }
81}
82
83#[derive(Clone)]
97pub struct Transition {
98 pub duration: Duration,
99 easing: Rc<dyn Fn(f32) -> f32>,
100 effects: SmallVec<[TransitionEffect; 2]>,
101}
102
103#[derive(Clone, Copy)]
104enum TransitionEffect {
105 SlideY(Pixels, Pixels),
106 SlideX(Pixels, Pixels),
107 Fade(f32, f32),
108 Width(Pixels, Pixels),
109 Height(Pixels, Pixels),
110}
111
112impl Transition {
113 pub fn new(duration: Duration) -> Self {
114 Self {
115 duration,
116 easing: Rc::new(ease_out_cubic),
117 effects: SmallVec::new(),
118 }
119 }
120
121 pub fn ease(mut self, easing: impl Fn(f32) -> f32 + 'static) -> Self {
123 self.easing = Rc::new(easing);
124 self
125 }
126
127 pub fn slide_y(mut self, from: Pixels, to: Pixels) -> Self {
129 self.effects.push(TransitionEffect::SlideY(from, to));
130 self
131 }
132
133 pub fn slide_x(mut self, from: Pixels, to: Pixels) -> Self {
135 self.effects.push(TransitionEffect::SlideX(from, to));
136 self
137 }
138
139 pub fn fade(mut self, from: f32, to: f32) -> Self {
141 self.effects.push(TransitionEffect::Fade(from, to));
142 self
143 }
144
145 pub fn width(mut self, from: Pixels, to: Pixels) -> Self {
147 self.effects.push(TransitionEffect::Width(from, to));
148 self
149 }
150
151 pub fn height(mut self, from: Pixels, to: Pixels) -> Self {
153 self.effects.push(TransitionEffect::Height(from, to));
154 self
155 }
156
157 pub fn apply<E: IntoElement + Styled + 'static>(
159 self,
160 element: E,
161 id: impl Into<ElementId>,
162 ) -> rgpui::AnimationElement<E> {
163 let animation = Animation::new(self.duration).with_easing({
164 let easing = self.easing.clone();
165 move |t| easing(t)
166 });
167 let effects = self.effects;
168 element.with_animation(id, animation, move |el, delta| {
169 let mut el = el;
170 for effect in &effects {
171 match effect {
172 TransitionEffect::SlideY(from, to) => {
173 el = el.top(Lerp::lerp(from, to, delta));
174 }
175 TransitionEffect::SlideX(from, to) => {
176 el = el.left(Lerp::lerp(from, to, delta));
177 }
178 TransitionEffect::Fade(from, to) => {
179 el = el.opacity(Lerp::lerp(from, to, delta));
180 }
181 TransitionEffect::Width(from, to) => {
182 el = el.w(Lerp::lerp(from, to, delta));
183 }
184 TransitionEffect::Height(from, to) => {
185 el = el.h(Lerp::lerp(from, to, delta));
186 }
187 }
188 }
189 el
190 })
191 }
192}
193
194impl FluentBuilder for Transition {}