1use gpui::{div, px, Div, IntoElement, ParentElement, Styled};
7use std::time::Duration;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum SlideDirection {
12 Left,
14 Right,
16 Up,
18 Down,
20}
21
22#[derive(Default)]
24pub enum Transition {
25 #[default]
27 None,
28
29 Fade {
31 duration_ms: u64,
33 },
34
35 Slide {
37 direction: SlideDirection,
39 duration_ms: u64,
41 },
42}
43
44impl std::fmt::Debug for Transition {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 match self {
47 Self::None => write!(f, "Transition::None"),
48 Self::Fade { duration_ms } => f
49 .debug_struct("Transition::Fade")
50 .field("duration_ms", duration_ms)
51 .finish(),
52 Self::Slide {
53 direction,
54 duration_ms,
55 } => f
56 .debug_struct("Transition::Slide")
57 .field("direction", direction)
58 .field("duration_ms", duration_ms)
59 .finish(),
60 }
61 }
62}
63
64impl Clone for Transition {
65 fn clone(&self) -> Self {
66 match self {
67 Self::None => Self::None,
68 Self::Fade { duration_ms } => Self::Fade {
69 duration_ms: *duration_ms,
70 },
71 Self::Slide {
72 direction,
73 duration_ms,
74 } => Self::Slide {
75 direction: *direction,
76 duration_ms: *duration_ms,
77 },
78 }
79 }
80}
81
82impl Transition {
83 pub fn fade(duration_ms: u64) -> Self {
85 Self::Fade { duration_ms }
86 }
87
88 pub fn slide_left(duration_ms: u64) -> Self {
90 Self::Slide {
91 direction: SlideDirection::Left,
92 duration_ms,
93 }
94 }
95
96 pub fn slide_right(duration_ms: u64) -> Self {
98 Self::Slide {
99 direction: SlideDirection::Right,
100 duration_ms,
101 }
102 }
103
104 pub fn slide_up(duration_ms: u64) -> Self {
106 Self::Slide {
107 direction: SlideDirection::Up,
108 duration_ms,
109 }
110 }
111
112 pub fn slide_down(duration_ms: u64) -> Self {
114 Self::Slide {
115 direction: SlideDirection::Down,
116 duration_ms,
117 }
118 }
119
120 pub fn duration(&self) -> Duration {
122 match self {
123 Self::None => Duration::ZERO,
124 Self::Fade { duration_ms, .. } => Duration::from_millis(*duration_ms),
125 Self::Slide { duration_ms, .. } => Duration::from_millis(*duration_ms),
126 }
127 }
128
129 pub fn is_none(&self) -> bool {
131 matches!(self, Self::None)
132 }
133}
134
135#[derive(Clone)]
137pub struct TransitionConfig {
138 pub default: Transition,
140
141 pub override_next: Option<Transition>,
143}
144
145impl Default for TransitionConfig {
146 fn default() -> Self {
147 Self {
148 default: Transition::None,
149 override_next: None,
150 }
151 }
152}
153
154impl TransitionConfig {
155 pub fn new(default: Transition) -> Self {
157 Self {
158 default,
159 override_next: None,
160 }
161 }
162
163 pub fn active(&self) -> &Transition {
165 self.override_next.as_ref().unwrap_or(&self.default)
166 }
167
168 pub fn set_override(&mut self, transition: Transition) {
170 self.override_next = Some(transition);
171 }
172
173 pub fn clear_override(&mut self) {
175 self.override_next = None;
176 }
177
178 pub fn has_override(&self) -> bool {
180 self.override_next.is_some()
181 }
182}
183
184pub struct TransitionContext {
190 pub animation: f32,
192 pub secondary_animation: f32,
194}
195
196pub fn apply_transition(element: impl IntoElement, transition: &Transition, progress: f32) -> Div {
201 let (x, y, opacity) = match transition {
204 Transition::None => (0.0, 0.0, 1.0),
205
206 Transition::Fade { .. } => {
207 (0.0, 0.0, progress)
209 }
210
211 Transition::Slide { direction, .. } => {
212 let offset_px = (1.0 - progress) * 100.0;
213 let (x, y) = match direction {
214 SlideDirection::Left => (offset_px, 0.0),
215 SlideDirection::Right => (-offset_px, 0.0),
216 SlideDirection::Up => (0.0, offset_px),
217 SlideDirection::Down => (0.0, -offset_px),
218 };
219 (x, y, progress)
220 }
221 };
222
223 div()
225 .relative()
226 .left(px(x))
227 .top(px(y))
228 .opacity(opacity)
229 .child(element)
230}
231
232pub fn ease_in_out_cubic(t: f32) -> f32 {
234 if t < 0.5 {
235 4.0 * t * t * t
236 } else {
237 1.0 - (-2.0 * t + 2.0).powi(3) / 2.0
238 }
239}
240
241pub fn apply_easing(progress: f32) -> f32 {
243 ease_in_out_cubic(progress.clamp(0.0, 1.0))
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249
250 #[test]
251 fn test_slide_direction() {
252 assert_eq!(SlideDirection::Left, SlideDirection::Left);
253 assert_ne!(SlideDirection::Left, SlideDirection::Right);
254 }
255
256 #[test]
257 fn test_transition_none() {
258 let transition = Transition::None;
259 assert!(transition.is_none());
260 assert_eq!(transition.duration(), Duration::ZERO);
261 }
262
263 #[test]
264 fn test_transition_fade() {
265 let transition = Transition::fade(200);
266 assert!(!transition.is_none());
267 assert_eq!(transition.duration(), Duration::from_millis(200));
268 }
269
270 #[test]
271 fn test_transition_slide() {
272 let transition = Transition::slide_left(300);
273 assert!(!transition.is_none());
274 assert_eq!(transition.duration(), Duration::from_millis(300));
275
276 if let Transition::Slide { direction, .. } = transition {
277 assert_eq!(direction, SlideDirection::Left);
278 } else {
279 panic!("Expected Slide transition");
280 }
281 }
282
283 #[test]
284 fn test_transition_config_default() {
285 let config = TransitionConfig::default();
286 assert!(config.active().is_none());
287 assert!(!config.has_override());
288 }
289
290 #[test]
291 fn test_transition_config_with_default() {
292 let config = TransitionConfig::new(Transition::fade(200));
293 assert!(!config.active().is_none());
294 assert!(!config.has_override());
295 }
296
297 #[test]
298 fn test_transition_config_override() {
299 let mut config = TransitionConfig::new(Transition::fade(200));
300
301 config.set_override(Transition::slide_left(300));
302 assert!(config.has_override());
303 assert_eq!(config.active().duration(), Duration::from_millis(300));
304
305 config.clear_override();
306 assert!(!config.has_override());
307 assert_eq!(config.active().duration(), Duration::from_millis(200));
308 }
309
310 #[test]
311 fn test_transition_helpers() {
312 let _ = Transition::fade(200);
314 let _ = Transition::slide_left(300);
315 let _ = Transition::slide_right(300);
316 let _ = Transition::slide_up(300);
317 let _ = Transition::slide_down(300);
318 }
319}